diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt index 4a288539cb..710288c925 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt @@ -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, 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( diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt index 57fc30efb3..2d535d9ce9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt @@ -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 diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillView.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillView.kt index bfedac947d..fe7d287215 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillView.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillView.kt @@ -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?, ) /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/ViewNodeTraversalData.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/ViewNodeTraversalData.kt index 7b52316f90..da66146d92 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/ViewNodeTraversalData.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/ViewNodeTraversalData.kt @@ -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, val idPackage: String?, val ignoreAutofillIds: List, - val website: String?, ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserImpl.kt index 524a5dfa63..65b2bf5bbc 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserImpl.kt @@ -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 = .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 = mutableListOf() val mutableIgnoreAutofillIdList: MutableList = 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, ) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt index e5ee01de22..03138c2a4f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt @@ -127,6 +127,7 @@ class AutofillCipherProviderImpl( password = cipherView.login?.password.orEmpty(), subtitle = cipherView.subtitle.orEmpty(), username = cipherView.login?.username.orEmpty(), + website = uri, ) } } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt index 55c05f0505..8271643d9a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt @@ -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) } +} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensions.kt index 4c100dd23e..d0d7b3d9a9 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensions.kt @@ -41,6 +41,7 @@ fun CipherView.toAutofillCipherProvider(): AutofillCipherProvider = password = login.password.orEmpty(), subtitle = subtitle.orEmpty(), username = login.username.orEmpty(), + website = uri, ), ) } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt index 2712940527..2a02fb0ea8 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt @@ -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, diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensions.kt index 67fa1bb47b..d9146ce378 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensions.kt @@ -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.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]. diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt index a7353b121a..fedde6dbc0 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt @@ -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, ), ), ), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt index 8d5b6d6752..4b5813ea9a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt @@ -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( diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderTest.kt index 7410a4c69b..c95e789182 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderTest.kt @@ -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( diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserTests.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserTests.kt index 6fe8caca6d..cd214fb8cd 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserTests.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/parser/AutofillParserTests.kt @@ -78,8 +78,9 @@ class AutofillParserTests { mockkStatic( FillRequest::getMaxInlineSuggestionsCount, FillRequest::getInlinePresentationSpecs, + AutofillView::buildUriOrNull, + List::buildPackageNameOrNull, ) - mockkStatic(List::buildUriOrNull) every { cardViewNode.website } returns WEBSITE every { loginViewNode.website } returns WEBSITE every { @@ -121,7 +122,7 @@ class AutofillParserTests { every { any>().buildPackageNameOrNull(assistStructure) } returns PACKAGE_NAME - every { any>().buildUriOrNull(PACKAGE_NAME) } returns URI + every { any().buildUriOrNull(PACKAGE_NAME) } returns URI parser = AutofillParserImpl( settingsRepository = settingsRepository, ) @@ -134,8 +135,9 @@ class AutofillParserTests { unmockkStatic( FillRequest::getMaxInlineSuggestionsCount, FillRequest::getInlinePresentationSpecs, + AutofillView::buildUriOrNull, + List::buildPackageNameOrNull, ) - unmockkStatic(List::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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().buildUriOrNull(PACKAGE_NAME) } verify(exactly = 0) { - any>().buildUriOrNull(ID_PACKAGE_ANDROID) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().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>().buildUriOrNull(PACKAGE_NAME) - } returns blockListedUri + every { any().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>().buildPackageNameOrNull(assistStructure) - any>().buildUriOrNull(PACKAGE_NAME) + any().buildUriOrNull(PACKAGE_NAME) } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt index 766be06c60..2bf663e7e0 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt @@ -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" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillPartitionExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillPartitionExtensionsTest.kt index 14b12dd348..18ed088c37 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillPartitionExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillPartitionExtensionsTest.kt @@ -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 diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt index d375e9d703..dd6dd7e69a 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt @@ -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" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensionsTest.kt index 56eabfd9e0..7fd598fdb1 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/CipherViewExtensionsTest.kt @@ -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"), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensionsTest.kt index 999e8223d0..d8fe3f651c 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensionsTest.kt @@ -75,6 +75,7 @@ class FilledDataExtensionsTest { isFocused = true, textValue = null, hasPasswordTerms = false, + website = "uri", ), ), ), diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt index 0e72abc0c5..2fec4139eb 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt @@ -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().isPasswordInputType } returns false + every { any().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().isPasswordInputType } returns false + every { any().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().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().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().isPasswordInputType } returns false every { any().isUsernameInputType } returns false every { viewNode.htmlInfo.hints() } returns emptyList() + every { viewNode.website } returns null } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensionsTest.kt index 34446a6e91..b1f2c48820 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/autofill/util/ViewNodeTraversalDataExtensionsTest.kt @@ -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" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AutofillCipherUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AutofillCipherUtil.kt index 59028c93ea..17a12dd5fd 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AutofillCipherUtil.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/sdk/model/AutofillCipherUtil.kt @@ -12,4 +12,5 @@ fun createMockPasswordCredentialAutofillCipherLogin() = AutofillCipher.Login( password = "mock-password", username = "mock-username", subtitle = "Subtitle", + website = "website", ) diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/autofill/util/AutofillUtilsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/autofill/util/AutofillUtilsTest.kt index 54cfdfae24..717f496c05 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/autofill/util/AutofillUtilsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/autofill/util/AutofillUtilsTest.kt @@ -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,