mirror of
https://github.com/bitwarden/android.git
synced 2025-12-10 00:06:22 -06:00
PM-26560: Fix cross-origin autofill issues (#5977)
This commit is contained in:
parent
5706ca2ba3
commit
0604d15d7d
@ -9,6 +9,7 @@ import com.x8bit.bitwarden.data.autofill.model.FilledData
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledPartition
|
||||
import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider
|
||||
import com.x8bit.bitwarden.data.autofill.util.buildFilledItemOrNull
|
||||
import com.x8bit.bitwarden.data.autofill.util.buildUri
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
@ -83,6 +84,7 @@ class FilledDataBuilderImpl(
|
||||
autofillCipher = autofillCipher,
|
||||
autofillViews = autofillRequest.partition.views,
|
||||
inlinePresentationSpec = getCipherInlinePresentationOrNull(),
|
||||
packageName = autofillRequest.packageName,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -96,7 +98,9 @@ class FilledDataBuilderImpl(
|
||||
?.getOrLastOrNull(inlineSuggestionsAdded)
|
||||
|
||||
return FilledData(
|
||||
filledPartitions = filledPartitions.take(n = MAX_FILLED_PARTITIONS_COUNT),
|
||||
filledPartitions = filledPartitions
|
||||
.filter { it.filledItems.isNotEmpty() }
|
||||
.take(n = MAX_FILLED_PARTITIONS_COUNT),
|
||||
ignoreAutofillIds = autofillRequest.ignoreAutofillIds,
|
||||
originalPartition = autofillRequest.partition,
|
||||
uri = autofillRequest.uri,
|
||||
@ -140,16 +144,21 @@ class FilledDataBuilderImpl(
|
||||
autofillCipher: AutofillCipher.Login,
|
||||
autofillViews: List<AutofillView.Login>,
|
||||
inlinePresentationSpec: InlinePresentationSpec?,
|
||||
packageName: String?,
|
||||
): FilledPartition {
|
||||
val filledItems = autofillViews
|
||||
.mapNotNull { autofillView ->
|
||||
val value = when (autofillView) {
|
||||
is AutofillView.Login.Username -> autofillCipher.username
|
||||
is AutofillView.Login.Password -> autofillCipher.password
|
||||
if (autofillView.data.website == autofillCipher.website ||
|
||||
buildUri(packageName.orEmpty(), "androidapp") == autofillCipher.website
|
||||
) {
|
||||
val value = when (autofillView) {
|
||||
is AutofillView.Login.Username -> autofillCipher.username
|
||||
is AutofillView.Login.Password -> autofillCipher.password
|
||||
}
|
||||
autofillView.buildFilledItemOrNull(value = value)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
autofillView.buildFilledItemOrNull(
|
||||
value = value,
|
||||
)
|
||||
}
|
||||
|
||||
return FilledPartition(
|
||||
|
||||
@ -66,6 +66,7 @@ sealed class AutofillCipher {
|
||||
override val subtitle: String,
|
||||
val password: String,
|
||||
val username: String,
|
||||
val website: String,
|
||||
) : AutofillCipher() {
|
||||
override val iconRes: Int
|
||||
@DrawableRes get() = BitwardenDrawable.ic_globe
|
||||
|
||||
@ -16,6 +16,7 @@ sealed class AutofillView {
|
||||
* @param isFocused Whether the view is currently focused.
|
||||
* @param textValue A text value that represents the input present in the field.
|
||||
* @param hasPasswordTerms Indicates that the field includes password terms.
|
||||
* @param website website associated with this view.
|
||||
*/
|
||||
data class Data(
|
||||
val autofillId: AutofillId,
|
||||
@ -24,6 +25,7 @@ sealed class AutofillView {
|
||||
val isFocused: Boolean,
|
||||
val textValue: String?,
|
||||
val hasPasswordTerms: Boolean,
|
||||
val website: String?,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@ -8,11 +8,9 @@ import android.view.autofill.AutofillId
|
||||
* @param autofillViews The list of views we care about for autofilling.
|
||||
* @param idPackage The package id for this view, if there is one.
|
||||
* @param ignoreAutofillIds The list of [AutofillId]s that should be ignored in the fill response.
|
||||
* @param website The website that is being displayed in the app, given there is one.
|
||||
*/
|
||||
data class ViewNodeTraversalData(
|
||||
val autofillViews: List<AutofillView>,
|
||||
val idPackage: String?,
|
||||
val ignoreAutofillIds: List<AutofillId>,
|
||||
val website: String?,
|
||||
)
|
||||
|
||||
@ -95,16 +95,21 @@ class AutofillParserImpl(
|
||||
.firstOrNull { it.data.isFocused }
|
||||
?: autofillViews.firstOrNull()
|
||||
|
||||
if (focusedView == null) {
|
||||
// The view is unfillable if there are no focused views.
|
||||
return AutofillRequest.Unfillable
|
||||
}
|
||||
|
||||
val packageName = traversalDataList.buildPackageNameOrNull(
|
||||
assistStructure = assistStructure,
|
||||
)
|
||||
val uri = traversalDataList.buildUriOrNull(
|
||||
val uri = focusedView.buildUriOrNull(
|
||||
packageName = packageName,
|
||||
)
|
||||
|
||||
val blockListedURIs = settingsRepository.blockedAutofillUris + BLOCK_LISTED_URIS
|
||||
if (focusedView == null || blockListedURIs.contains(uri)) {
|
||||
// The view is unfillable if there are no focused views or the URI is block listed.
|
||||
if (blockListedURIs.contains(uri)) {
|
||||
// The view is unfillable if the URI is block listed.
|
||||
return AutofillRequest.Unfillable
|
||||
}
|
||||
|
||||
@ -165,7 +170,7 @@ private fun AssistStructure.traverse(): List<ViewNodeTraversalData> =
|
||||
.mapNotNull { windowNode ->
|
||||
windowNode
|
||||
.rootViewNode
|
||||
?.traverse()
|
||||
?.traverse(parentWebsite = null)
|
||||
?.updateForMissingPasswordFields()
|
||||
?.updateForMissingUsernameFields()
|
||||
}
|
||||
@ -243,16 +248,17 @@ private fun ViewNodeTraversalData.copyAndMapAutofillViews(
|
||||
* Recursively traverse this [AssistStructure.ViewNode] and all of its descendants. Convert the
|
||||
* data into [ViewNodeTraversalData].
|
||||
*/
|
||||
private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
|
||||
private fun AssistStructure.ViewNode.traverse(
|
||||
parentWebsite: String?,
|
||||
): ViewNodeTraversalData {
|
||||
// Set up mutable lists for collecting valid AutofillViews and ignorable view ids.
|
||||
val mutableAutofillViewList: MutableList<AutofillView> = mutableListOf()
|
||||
val mutableIgnoreAutofillIdList: MutableList<AutofillId> = mutableListOf()
|
||||
var idPackage: String? = this.idPackage
|
||||
var website: String? = this.website
|
||||
|
||||
// Try converting this `ViewNode` into an `AutofillView`. If a valid instance is returned, add
|
||||
// it to the list. Otherwise, ignore the `AutofillId` associated with this `ViewNode`.
|
||||
toAutofillView()
|
||||
toAutofillView(parentWebsite = parentWebsite)
|
||||
?.run(mutableAutofillViewList::add)
|
||||
?: autofillId?.run(mutableIgnoreAutofillIdList::add)
|
||||
|
||||
@ -260,7 +266,7 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
|
||||
for (i in 0 until childCount) {
|
||||
// Extract the traversal data from each child view node and add it to the lists.
|
||||
getChildAt(i)
|
||||
.traverse()
|
||||
.traverse(parentWebsite = website)
|
||||
.let { viewNodeTraversalData ->
|
||||
viewNodeTraversalData.autofillViews.forEach(mutableAutofillViewList::add)
|
||||
viewNodeTraversalData.ignoreAutofillIds.forEach(mutableIgnoreAutofillIdList::add)
|
||||
@ -273,10 +279,6 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
|
||||
) {
|
||||
idPackage = viewNodeTraversalData.idPackage
|
||||
}
|
||||
// Get the first non-null website.
|
||||
if (website == null) {
|
||||
website = viewNodeTraversalData.website
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -286,6 +288,5 @@ private fun AssistStructure.ViewNode.traverse(): ViewNodeTraversalData {
|
||||
autofillViews = mutableAutofillViewList,
|
||||
idPackage = idPackage,
|
||||
ignoreAutofillIds = mutableIgnoreAutofillIdList,
|
||||
website = website,
|
||||
)
|
||||
}
|
||||
|
||||
@ -127,6 +127,7 @@ class AutofillCipherProviderImpl(
|
||||
password = cipherView.login?.password.orEmpty(),
|
||||
subtitle = cipherView.subtitle.orEmpty(),
|
||||
username = cipherView.login?.username.orEmpty(),
|
||||
website = uri,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,15 @@ import android.view.View
|
||||
import android.view.autofill.AutofillValue
|
||||
import com.x8bit.bitwarden.data.autofill.model.AutofillView
|
||||
import com.x8bit.bitwarden.data.autofill.model.FilledItem
|
||||
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.findVaultCardBrandWithNameOrNull
|
||||
|
||||
/**
|
||||
* The android app URI scheme. Example: androidapp://com.x8bit.bitwarden
|
||||
*/
|
||||
private const val ANDROID_APP_SCHEME: String = "androidapp"
|
||||
|
||||
/**
|
||||
* Convert this [AutofillView] into a [FilledItem]. Return null if not possible.
|
||||
*/
|
||||
@ -96,3 +102,17 @@ private fun AutofillView.buildListAutofillValueOrNull(
|
||||
?.let { AutofillValue.forList(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and build a URI. First, try building a website from the list of [ViewNodeTraversalData]. If
|
||||
* that fails, try converting [packageName] into an Android app URI.
|
||||
*/
|
||||
fun AutofillView.buildUriOrNull(
|
||||
packageName: String?,
|
||||
): String? {
|
||||
// Search list of ViewNodeTraversalData for a website URI.
|
||||
this.data.website?.let { websiteUri -> return websiteUri }
|
||||
|
||||
// If the package name is available, build a URI out of that.
|
||||
return packageName?.let { buildUri(domain = it, scheme = ANDROID_APP_SCHEME) }
|
||||
}
|
||||
|
||||
@ -41,6 +41,7 @@ fun CipherView.toAutofillCipherProvider(): AutofillCipherProvider =
|
||||
password = login.password.orEmpty(),
|
||||
subtitle = subtitle.orEmpty(),
|
||||
username = login.username.orEmpty(),
|
||||
website = uri,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@ -49,7 +49,9 @@ private val AssistStructure.ViewNode.isInputField: Boolean
|
||||
* doesn't contain a valid autofillId, it isn't an a view setup for autofill, so we return null. If
|
||||
* it doesn't have a supported hint and isn't an input field, we also return null.
|
||||
*/
|
||||
fun AssistStructure.ViewNode.toAutofillView(): AutofillView? =
|
||||
fun AssistStructure.ViewNode.toAutofillView(
|
||||
parentWebsite: String?,
|
||||
): AutofillView? =
|
||||
this
|
||||
.autofillId
|
||||
// We only care about nodes with a valid `AutofillId`.
|
||||
@ -67,6 +69,7 @@ fun AssistStructure.ViewNode.toAutofillView(): AutofillView? =
|
||||
isFocused = this.isFocused,
|
||||
textValue = this.autofillValue?.extractTextValue(),
|
||||
hasPasswordTerms = this.hasPasswordTerms(),
|
||||
website = this.website ?: parentWebsite,
|
||||
)
|
||||
buildAutofillView(
|
||||
autofillOptions = autofillOptions,
|
||||
|
||||
@ -4,36 +4,6 @@ import android.app.assist.AssistStructure
|
||||
import com.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||
|
||||
/**
|
||||
* The android app URI scheme. Example: androidapp://com.x8bit.bitwarden
|
||||
*/
|
||||
private const val ANDROID_APP_SCHEME: String = "androidapp"
|
||||
|
||||
/**
|
||||
* Try and build a URI. First, try building a website from the list of [ViewNodeTraversalData]. If
|
||||
* that fails, try converting [packageName] into an Android app URI.
|
||||
*/
|
||||
fun List<ViewNodeTraversalData>.buildUriOrNull(
|
||||
packageName: String?,
|
||||
): String? {
|
||||
// Search list of ViewNodeTraversalData for a website URI.
|
||||
this
|
||||
.firstOrNull { it.website != null }
|
||||
?.website
|
||||
?.let { websiteUri ->
|
||||
return websiteUri
|
||||
}
|
||||
|
||||
// If the package name is available, build a URI out of that.
|
||||
return packageName
|
||||
?.let { nonNullPackageName ->
|
||||
buildUri(
|
||||
domain = nonNullPackageName,
|
||||
scheme = ANDROID_APP_SCHEME,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and build a package name. First, try searching traversal data for package names. If that
|
||||
* fails, try extracting a package name from [assistStructure].
|
||||
|
||||
@ -145,6 +145,7 @@ class FillResponseBuilderTest {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -246,6 +247,7 @@ class FillResponseBuilderTest {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -61,16 +61,20 @@ class FilledDataBuilderTest {
|
||||
password = password,
|
||||
username = username,
|
||||
subtitle = "Subtitle",
|
||||
website = URI,
|
||||
)
|
||||
val filledItemPassword: FilledItem = mockk()
|
||||
val filledItemUsername: FilledItem = mockk()
|
||||
val autofillViewPassword: AutofillView.Login.Password = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(password) } returns filledItemPassword
|
||||
}
|
||||
val autofillViewUsernameOne: AutofillView.Login.Username = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(username) } returns filledItemUsername
|
||||
}
|
||||
val autofillViewUsernameTwo: AutofillView.Login.Username = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(username) } returns null
|
||||
}
|
||||
val autofillPartition = AutofillPartition.Login(
|
||||
@ -341,15 +345,8 @@ class FilledDataBuilderTest {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
val filledPartition = FilledPartition(
|
||||
autofillCipher = autofillCipher,
|
||||
filledItems = emptyList(),
|
||||
inlinePresentationSpec = null,
|
||||
)
|
||||
val expected = FilledData(
|
||||
filledPartitions = listOf(
|
||||
filledPartition,
|
||||
),
|
||||
filledPartitions = emptyList(),
|
||||
ignoreAutofillIds = ignoreAutofillIds,
|
||||
originalPartition = autofillPartition,
|
||||
uri = URI,
|
||||
@ -396,14 +393,17 @@ class FilledDataBuilderTest {
|
||||
password = password,
|
||||
username = username,
|
||||
subtitle = "Subtitle",
|
||||
website = URI,
|
||||
)
|
||||
|
||||
val filledItemPassword: FilledItem = mockk()
|
||||
val filledItemUsername: FilledItem = mockk()
|
||||
val autofillViewPassword: AutofillView.Login.Password = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(password) } returns filledItemPassword
|
||||
}
|
||||
val autofillViewUsername: AutofillView.Login.Username = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(username) } returns filledItemUsername
|
||||
}
|
||||
val autofillPartition = AutofillPartition.Login(
|
||||
@ -490,13 +490,16 @@ class FilledDataBuilderTest {
|
||||
password = password,
|
||||
username = username,
|
||||
subtitle = "Subtitle",
|
||||
website = URI,
|
||||
)
|
||||
val filledItemPassword: FilledItem = mockk()
|
||||
val filledItemUsername: FilledItem = mockk()
|
||||
val autofillViewPassword: AutofillView.Login.Password = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(password) } returns filledItemPassword
|
||||
}
|
||||
val autofillViewUsername: AutofillView.Login.Username = mockk {
|
||||
every { data } returns mockk { every { website } returns URI }
|
||||
every { buildFilledItemOrNull(username) } returns filledItemUsername
|
||||
}
|
||||
val autofillPartition = AutofillPartition.Login(
|
||||
|
||||
@ -35,6 +35,7 @@ class SaveInfoBuilderTest {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
)
|
||||
private val autofillIdValid: AutofillId = mockk()
|
||||
private val autofillViewDataValid = AutofillView.Data(
|
||||
@ -44,6 +45,7 @@ class SaveInfoBuilderTest {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
)
|
||||
private val autofillPartitionCard: AutofillPartition.Card = AutofillPartition.Card(
|
||||
views = listOf(
|
||||
|
||||
@ -78,8 +78,9 @@ class AutofillParserTests {
|
||||
mockkStatic(
|
||||
FillRequest::getMaxInlineSuggestionsCount,
|
||||
FillRequest::getInlinePresentationSpecs,
|
||||
AutofillView::buildUriOrNull,
|
||||
List<ViewNodeTraversalData>::buildPackageNameOrNull,
|
||||
)
|
||||
mockkStatic(List<ViewNodeTraversalData>::buildUriOrNull)
|
||||
every { cardViewNode.website } returns WEBSITE
|
||||
every { loginViewNode.website } returns WEBSITE
|
||||
every {
|
||||
@ -121,7 +122,7 @@ class AutofillParserTests {
|
||||
every {
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
} returns PACKAGE_NAME
|
||||
every { any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME) } returns URI
|
||||
every { any<AutofillView>().buildUriOrNull(PACKAGE_NAME) } returns URI
|
||||
parser = AutofillParserImpl(
|
||||
settingsRepository = settingsRepository,
|
||||
)
|
||||
@ -134,8 +135,9 @@ class AutofillParserTests {
|
||||
unmockkStatic(
|
||||
FillRequest::getMaxInlineSuggestionsCount,
|
||||
FillRequest::getInlinePresentationSpecs,
|
||||
AutofillView::buildUriOrNull,
|
||||
List<ViewNodeTraversalData>::buildPackageNameOrNull,
|
||||
)
|
||||
unmockkStatic(List<ViewNodeTraversalData>::buildUriOrNull)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -181,7 +183,7 @@ class AutofillParserTests {
|
||||
every { this@mockk.childCount } returns 0
|
||||
every { this@mockk.idPackage } returns null
|
||||
every { this@mockk.isFocused } returns false
|
||||
every { this@mockk.toAutofillView() } returns null
|
||||
every { this@mockk.toAutofillView(parentWebsite = any()) } returns null
|
||||
every { this@mockk.website } returns null
|
||||
}
|
||||
// `invalidChildViewNode` simulates the OS assigning a node's idPackage to "android", which
|
||||
@ -194,7 +196,7 @@ class AutofillParserTests {
|
||||
every { this@mockk.childCount } returns 0
|
||||
every { this@mockk.idPackage } returns ID_PACKAGE_ANDROID
|
||||
every { this@mockk.isFocused } returns false
|
||||
every { this@mockk.toAutofillView() } returns null
|
||||
every { this@mockk.toAutofillView(parentWebsite = any()) } returns null
|
||||
every { this@mockk.website } returns null
|
||||
}
|
||||
val parentAutofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
|
||||
@ -207,6 +209,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -214,7 +217,7 @@ class AutofillParserTests {
|
||||
every { this@mockk.autofillHints } returns arrayOf(parentAutofillHint)
|
||||
every { this@mockk.autofillId } returns parentAutofillId
|
||||
every { this@mockk.idPackage } returns null
|
||||
every { this@mockk.toAutofillView() } returns parentAutofillView
|
||||
every { this@mockk.toAutofillView(parentWebsite = any()) } returns parentAutofillView
|
||||
every { this@mockk.childCount } returns 2
|
||||
every { this@mockk.getChildAt(0) } returns childViewNode
|
||||
every { this@mockk.getChildAt(1) } returns invalidChildViewNode
|
||||
@ -255,10 +258,10 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
verify(exactly = 0) {
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(ID_PACKAGE_ANDROID)
|
||||
any<AutofillView>().buildUriOrNull(ID_PACKAGE_ANDROID)
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,6 +277,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -285,6 +289,7 @@ class AutofillParserTests {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Card(
|
||||
@ -298,8 +303,8 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -319,7 +324,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,6 +340,7 @@ class AutofillParserTests {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -346,6 +352,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Login(
|
||||
@ -359,8 +366,8 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -380,7 +387,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -398,6 +405,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = true,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val loginAutofillView: AutofillView.Login = AutofillView.Login.Password(
|
||||
@ -414,7 +422,7 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { loginViewNode.toAutofillView() } returns unusedAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns unusedAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -434,7 +442,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -481,6 +489,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val loginUsernameAutofillView: AutofillView.Login = AutofillView.Login.Username(
|
||||
@ -491,6 +500,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val loginPasswordAutofillView: AutofillView.Login = AutofillView.Login.Password(
|
||||
@ -501,6 +511,7 @@ class AutofillParserTests {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Login(
|
||||
@ -514,9 +525,13 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { rootViewNode.toAutofillView() } returns null
|
||||
every { hiddenUserNameViewNode.toAutofillView() } returns unusedAutofillView
|
||||
every { passwordViewNode.toAutofillView() } returns loginPasswordAutofillView
|
||||
every { rootViewNode.toAutofillView(parentWebsite = any()) } returns null
|
||||
every {
|
||||
hiddenUserNameViewNode.toAutofillView(parentWebsite = any())
|
||||
} returns unusedAutofillView
|
||||
every {
|
||||
passwordViewNode.toAutofillView(parentWebsite = any())
|
||||
} returns loginPasswordAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -536,7 +551,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -552,6 +567,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -563,6 +579,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Card(
|
||||
@ -576,8 +593,8 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -597,7 +614,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -614,6 +631,7 @@ class AutofillParserTests {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -625,6 +643,7 @@ class AutofillParserTests {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Card(
|
||||
@ -638,8 +657,8 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -659,7 +678,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = true,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -676,6 +695,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -687,6 +707,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Card(
|
||||
@ -700,8 +721,8 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -721,7 +742,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = false,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -738,6 +759,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -749,6 +771,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val autofillPartition = AutofillPartition.Card(
|
||||
@ -762,8 +785,8 @@ class AutofillParserTests {
|
||||
partition = autofillPartition,
|
||||
uri = URI,
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -783,7 +806,7 @@ class AutofillParserTests {
|
||||
isInlineAutofillEnabled = false,
|
||||
)
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@ -799,6 +822,7 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
monthValue = null,
|
||||
)
|
||||
@ -810,22 +834,21 @@ class AutofillParserTests {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = URI,
|
||||
),
|
||||
)
|
||||
val remoteBlockList = listOf(
|
||||
"blockListedUri.com",
|
||||
"blockListedAgainUri.com",
|
||||
)
|
||||
every { cardViewNode.toAutofillView() } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView() } returns loginAutofillView
|
||||
every { cardViewNode.toAutofillView(parentWebsite = any()) } returns cardAutofillView
|
||||
every { loginViewNode.toAutofillView(parentWebsite = any()) } returns loginAutofillView
|
||||
every { settingsRepository.blockedAutofillUris } returns remoteBlockList
|
||||
|
||||
// A function for asserting that a block listed URI results in an unfillable request.
|
||||
fun testBlockListedUri(blockListedUri: String) {
|
||||
// Setup
|
||||
every {
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
} returns blockListedUri
|
||||
every { any<AutofillView>().buildUriOrNull(PACKAGE_NAME) } returns blockListedUri
|
||||
|
||||
// Test
|
||||
val actual = parser.parse(
|
||||
@ -844,7 +867,7 @@ class AutofillParserTests {
|
||||
// Verify all tests
|
||||
verify(exactly = BLOCK_LISTED_URIS.size + remoteBlockList.size) {
|
||||
any<List<ViewNodeTraversalData>>().buildPackageNameOrNull(assistStructure)
|
||||
any<List<ViewNodeTraversalData>>().buildUriOrNull(PACKAGE_NAME)
|
||||
any<AutofillView>().buildUriOrNull(PACKAGE_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -604,6 +604,7 @@ private const val LOGIN_NAME = "John's Login"
|
||||
private const val LOGIN_PASSWORD = "Password123"
|
||||
private const val LOGIN_SUBTITLE = "John Doe"
|
||||
private const val LOGIN_USERNAME = "John-Bitwarden"
|
||||
private const val URI: String = "androidapp://com.x8bit.bitwarden"
|
||||
private val LOGIN_AUTOFILL_CIPHER_WITH_TOTP = AutofillCipher.Login(
|
||||
cipherId = LOGIN_WITH_TOTP_CIPHER_ID,
|
||||
isTotpEnabled = true,
|
||||
@ -611,6 +612,7 @@ private val LOGIN_AUTOFILL_CIPHER_WITH_TOTP = AutofillCipher.Login(
|
||||
password = LOGIN_PASSWORD,
|
||||
subtitle = LOGIN_SUBTITLE,
|
||||
username = LOGIN_USERNAME,
|
||||
website = URI,
|
||||
)
|
||||
private val LOGIN_AUTOFILL_CIPHER_WITHOUT_TOTP = AutofillCipher.Login(
|
||||
cipherId = LOGIN_WITHOUT_TOTP_CIPHER_ID,
|
||||
@ -619,5 +621,5 @@ private val LOGIN_AUTOFILL_CIPHER_WITHOUT_TOTP = AutofillCipher.Login(
|
||||
password = LOGIN_PASSWORD,
|
||||
subtitle = LOGIN_SUBTITLE,
|
||||
username = LOGIN_USERNAME,
|
||||
website = URI,
|
||||
)
|
||||
private const val URI: String = "androidapp://com.x8bit.bitwarden"
|
||||
|
||||
@ -16,6 +16,7 @@ class AutofillPartitionExtensionsTest {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
)
|
||||
private val autofillDataValidText: AutofillView.Data = AutofillView.Data(
|
||||
autofillId = mockk(),
|
||||
@ -24,6 +25,7 @@ class AutofillPartitionExtensionsTest {
|
||||
isFocused = false,
|
||||
textValue = TEXT_VALUE,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
)
|
||||
|
||||
//region Card tests
|
||||
|
||||
@ -28,6 +28,7 @@ class AutofillViewExtensionsTest {
|
||||
isFocused = false,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
)
|
||||
|
||||
@BeforeEach
|
||||
@ -349,4 +350,47 @@ class AutofillViewExtensionsTest {
|
||||
// Verify
|
||||
assertNull(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildUriOrNull should return website URI when present`() {
|
||||
// Setup
|
||||
val autofillViewData = autofillViewData.copy(website = WEBSITE)
|
||||
val autofillView = AutofillView.Login.Username(data = autofillViewData)
|
||||
|
||||
// Test
|
||||
val actual = autofillView.buildUriOrNull(packageName = PACKAGE_NAME)
|
||||
|
||||
// Verify
|
||||
assertEquals(WEBSITE, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildUriOrNull should return package name URI when website is null`() {
|
||||
// Setup
|
||||
val autofillViewData = autofillViewData.copy(website = null)
|
||||
val autofillView = AutofillView.Login.Username(data = autofillViewData)
|
||||
val expected = "androidapp://$PACKAGE_NAME"
|
||||
|
||||
// Test
|
||||
val actual = autofillView.buildUriOrNull(packageName = PACKAGE_NAME)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildUriOrNull should return null when website and packageName are null`() {
|
||||
// Setup
|
||||
val autofillViewData = autofillViewData.copy(website = null)
|
||||
val autofillView = AutofillView.Login.Username(data = autofillViewData)
|
||||
|
||||
// Test
|
||||
val actual = autofillView.buildUriOrNull(packageName = null)
|
||||
|
||||
// Verify
|
||||
assertNull(actual)
|
||||
}
|
||||
}
|
||||
|
||||
private const val PACKAGE_NAME: String = "com.google"
|
||||
private const val WEBSITE: String = "https://www.google.com"
|
||||
|
||||
@ -39,6 +39,7 @@ class CipherViewExtensionsTest {
|
||||
subtitle = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
username = "mockUsername-1",
|
||||
website = "uri",
|
||||
),
|
||||
),
|
||||
autofillCipherProvider.getLoginAutofillCiphers(uri = "uri"),
|
||||
@ -71,6 +72,7 @@ class CipherViewExtensionsTest {
|
||||
subtitle = "mockUsername-1",
|
||||
password = "mockPassword-1",
|
||||
username = "mockUsername-1",
|
||||
website = "uri",
|
||||
),
|
||||
),
|
||||
autofillCipherProvider.getLoginAutofillCiphers(uri = "uri"),
|
||||
|
||||
@ -75,6 +75,7 @@ class FilledDataExtensionsTest {
|
||||
isFocused = true,
|
||||
textValue = null,
|
||||
hasPasswordTerms = false,
|
||||
website = "uri",
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -29,6 +29,7 @@ class ViewNodeExtensionsTest {
|
||||
isFocused = expectedIsFocused,
|
||||
textValue = TEXT_VALUE,
|
||||
hasPasswordTerms = false,
|
||||
website = null,
|
||||
)
|
||||
private val testAutofillValue: AutofillValue = mockk()
|
||||
private val mockHtmlInfo: HtmlInfo = mockk {
|
||||
@ -46,10 +47,12 @@ class ViewNodeExtensionsTest {
|
||||
every { inputType } returns 1
|
||||
every { isFocused } returns expectedIsFocused
|
||||
every { htmlInfo } returns mockHtmlInfo
|
||||
every { website } returns null
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
mockkStatic(AssistStructure.ViewNode::website)
|
||||
mockkStatic(HtmlInfo::isInputField)
|
||||
mockkStatic(HtmlInfo::isPasswordField)
|
||||
mockkStatic(Int::isPasswordInputType)
|
||||
@ -72,6 +75,7 @@ class ViewNodeExtensionsTest {
|
||||
|
||||
@AfterEach
|
||||
fun teardown() {
|
||||
unmockkStatic(AssistStructure.ViewNode::website)
|
||||
unmockkStatic(HtmlInfo::isInputField)
|
||||
unmockkStatic(HtmlInfo::isPasswordField)
|
||||
unmockkStatic(Int::isPasswordInputType)
|
||||
@ -93,7 +97,7 @@ class ViewNodeExtensionsTest {
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -121,7 +125,7 @@ class ViewNodeExtensionsTest {
|
||||
} returns monthValue
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -137,7 +141,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.htmlInfo.hints() } returns SUPPORTED_RAW_CARD_EXP_MONTH_HINTS
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -152,7 +156,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARD_EXP_MONTH_HINTS.forEach { idEntry ->
|
||||
every { viewNode.idEntry } returns idEntry
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for idEntry: $idEntry")
|
||||
}
|
||||
@ -167,7 +171,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
SUPPORTED_RAW_CARD_EXP_MONTH_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
}
|
||||
@ -182,7 +186,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -197,7 +201,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARD_EXP_YEAR_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
@ -213,7 +217,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.htmlInfo.hints() } returns SUPPORTED_RAW_CARD_EXP_YEAR_HINTS
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -227,7 +231,7 @@ class ViewNodeExtensionsTest {
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||
every { mockHtmlInfo.isInputField } returns true
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -241,7 +245,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARD_EXP_DATE_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
@ -256,7 +260,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.htmlInfo.hints() } returns SUPPORTED_RAW_CARD_EXP_DATE_HINTS
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -271,7 +275,7 @@ class ViewNodeExtensionsTest {
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -286,7 +290,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARD_NUMBER_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
@ -300,7 +304,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.htmlInfo.hints() } returns SUPPORTED_RAW_CARD_NUMBER_HINTS
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -315,7 +319,7 @@ class ViewNodeExtensionsTest {
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -330,7 +334,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARD_SECURITY_CODE_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
@ -344,7 +348,7 @@ class ViewNodeExtensionsTest {
|
||||
data = autofillViewData,
|
||||
)
|
||||
every { viewNode.htmlInfo.hints() } returns SUPPORTED_RAW_CARD_SECURITY_CODE_HINTS
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@ -357,7 +361,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARDHOLDER_NAME_HINTS.forEach { idEntry ->
|
||||
every { viewNode.idEntry } returns idEntry
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for idEntry: $idEntry")
|
||||
}
|
||||
@ -372,7 +376,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_CARDHOLDER_NAME_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
@ -386,7 +390,7 @@ class ViewNodeExtensionsTest {
|
||||
data = autofillViewData,
|
||||
)
|
||||
every { viewNode.htmlInfo.hints() } returns SUPPORTED_RAW_CARDHOLDER_NAME_HINTS
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@ -399,7 +403,7 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHint)
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -413,7 +417,7 @@ class ViewNodeExtensionsTest {
|
||||
SUPPORTED_RAW_PASSWORD_HINTS.forEach { hint ->
|
||||
every { viewNode.hint } returns hint
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual, "Failed for hint: $hint")
|
||||
}
|
||||
@ -428,11 +432,50 @@ class ViewNodeExtensionsTest {
|
||||
)
|
||||
every { viewNode.htmlInfo.isPasswordField() } returns true
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toAutofillView should return AutofillView Login Username with internal website`() {
|
||||
// Setup
|
||||
val website = "website"
|
||||
val expected = AutofillView.Login.Username(
|
||||
data = autofillViewData.copy(website = website),
|
||||
)
|
||||
setupUnsupportedInputFieldViewNode()
|
||||
every { viewNode.website } returns website
|
||||
every { viewNode.className } returns "android.widget.EditText"
|
||||
every { any<Int>().isPasswordInputType } returns false
|
||||
every { any<Int>().isUsernameInputType } returns true
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toAutofillView should return AutofillView Login Username with external website`() {
|
||||
// Setup
|
||||
val website = "website"
|
||||
val expected = AutofillView.Login.Username(
|
||||
data = autofillViewData.copy(website = website),
|
||||
)
|
||||
setupUnsupportedInputFieldViewNode()
|
||||
every { viewNode.className } returns "android.widget.EditText"
|
||||
every { any<Int>().isPasswordInputType } returns false
|
||||
every { any<Int>().isUsernameInputType } returns true
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView(parentWebsite = website)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toAutofillView should return AutofillView Login Username when is EditText and isUsernameField`() {
|
||||
@ -446,7 +489,7 @@ class ViewNodeExtensionsTest {
|
||||
every { any<Int>().isUsernameInputType } returns true
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -465,7 +508,7 @@ class ViewNodeExtensionsTest {
|
||||
every { any<Int>().isUsernameInputType } returns true
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -480,7 +523,7 @@ class ViewNodeExtensionsTest {
|
||||
every { viewNode.htmlInfo.isInputField } returns false
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertNull(actual)
|
||||
@ -494,7 +537,7 @@ class ViewNodeExtensionsTest {
|
||||
data = autofillViewData,
|
||||
)
|
||||
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
@ -511,7 +554,7 @@ class ViewNodeExtensionsTest {
|
||||
every { viewNode.autofillHints } returns arrayOf(autofillHintOne, autofillHintTwo)
|
||||
|
||||
// Test
|
||||
val actual = viewNode.toAutofillView()
|
||||
val actual = viewNode.toAutofillView(parentWebsite = null)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
@ -1207,6 +1250,7 @@ class ViewNodeExtensionsTest {
|
||||
every { any<Int>().isPasswordInputType } returns false
|
||||
every { any<Int>().isUsernameInputType } returns false
|
||||
every { viewNode.htmlInfo.hints() } returns emptyList()
|
||||
every { viewNode.website } returns null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertNull
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class ViewNodeTraversalDataExtensionsTest {
|
||||
@ -15,64 +14,6 @@ class ViewNodeTraversalDataExtensionsTest {
|
||||
every { this@mockk.getWindowNodeAt(0) } returns windowNode
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildUriOrNull should return website URI when present`() {
|
||||
// Setup
|
||||
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||
autofillViews = emptyList(),
|
||||
idPackage = null,
|
||||
ignoreAutofillIds = emptyList(),
|
||||
website = WEBSITE,
|
||||
)
|
||||
|
||||
// Test
|
||||
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||
packageName = PACKAGE_NAME,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertEquals(WEBSITE, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildUriOrNull should return package name URI when website is null`() {
|
||||
// Setup
|
||||
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||
autofillViews = emptyList(),
|
||||
idPackage = null,
|
||||
ignoreAutofillIds = emptyList(),
|
||||
website = null,
|
||||
)
|
||||
val expected = "androidapp://$PACKAGE_NAME"
|
||||
|
||||
// Test
|
||||
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||
packageName = PACKAGE_NAME,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildUriOrNull should return null when website and packageName are null`() {
|
||||
// Setup
|
||||
val viewNodeTraversalData = ViewNodeTraversalData(
|
||||
autofillViews = emptyList(),
|
||||
idPackage = null,
|
||||
ignoreAutofillIds = emptyList(),
|
||||
website = null,
|
||||
)
|
||||
|
||||
// Test
|
||||
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
|
||||
packageName = null,
|
||||
)
|
||||
|
||||
// Verify
|
||||
assertNull(actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `buildPackageNameOrNull should return idPackage when available`() {
|
||||
// Setup
|
||||
@ -80,7 +21,6 @@ class ViewNodeTraversalDataExtensionsTest {
|
||||
autofillViews = emptyList(),
|
||||
idPackage = ID_PACKAGE,
|
||||
ignoreAutofillIds = emptyList(),
|
||||
website = null,
|
||||
)
|
||||
|
||||
// Test
|
||||
@ -99,7 +39,6 @@ class ViewNodeTraversalDataExtensionsTest {
|
||||
autofillViews = emptyList(),
|
||||
idPackage = null,
|
||||
ignoreAutofillIds = emptyList(),
|
||||
website = null,
|
||||
)
|
||||
val expected = "com.x8bit.bitwarden"
|
||||
every { windowNode.title } returns "com.x8bit.bitwarden/path.deeper.into.app"
|
||||
@ -115,5 +54,3 @@ class ViewNodeTraversalDataExtensionsTest {
|
||||
}
|
||||
|
||||
private const val ID_PACKAGE: String = "com.x8bit.bitwarden"
|
||||
private const val PACKAGE_NAME: String = "com.google"
|
||||
private const val WEBSITE: String = "https://www.google.com"
|
||||
|
||||
@ -12,4 +12,5 @@ fun createMockPasswordCredentialAutofillCipherLogin() = AutofillCipher.Login(
|
||||
password = "mock-password",
|
||||
username = "mock-username",
|
||||
subtitle = "Subtitle",
|
||||
website = "website",
|
||||
)
|
||||
|
||||
@ -87,6 +87,7 @@ class AutofillUtilsTest {
|
||||
password = "password",
|
||||
username = "username",
|
||||
subtitle = "Subtitle",
|
||||
website = "website",
|
||||
),
|
||||
second = AutofillAppInfo(
|
||||
context = context,
|
||||
@ -103,6 +104,7 @@ class AutofillUtilsTest {
|
||||
password = "password",
|
||||
username = "username",
|
||||
subtitle = "AmazonSubtitle",
|
||||
website = "website",
|
||||
),
|
||||
second = AutofillAppInfo(
|
||||
context = context,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user