Add URI generation algorithm to autofill parsing (#582)

This commit is contained in:
Lucas Kivi
2024-01-12 12:45:56 -06:00
committed by GitHub
parent c5a3340ac5
commit 2f3435b55b
10 changed files with 549 additions and 12 deletions

View File

@@ -27,7 +27,10 @@ class FilledDataBuilderTest {
val autofillId: AutofillId = mockk()
val autofillView = AutofillView.Login.Username(
autofillId = autofillId,
idPackage = null,
isFocused = false,
webDomain = null,
webScheme = null,
)
val autofillPartition = AutofillPartition.Login(
views = listOf(autofillView),
@@ -36,6 +39,7 @@ class FilledDataBuilderTest {
val autofillRequest = AutofillRequest.Fillable(
ignoreAutofillIds = ignoreAutofillIds,
partition = autofillPartition,
uri = URI,
)
val filledItem = FilledItem(
autofillId = autofillId,
@@ -67,7 +71,10 @@ class FilledDataBuilderTest {
val autofillId: AutofillId = mockk()
val autofillView = AutofillView.Card.Number(
autofillId = autofillId,
idPackage = null,
isFocused = false,
webDomain = null,
webScheme = null,
)
val autofillPartition = AutofillPartition.Card(
views = listOf(autofillView),
@@ -76,6 +83,7 @@ class FilledDataBuilderTest {
val autofillRequest = AutofillRequest.Fillable(
ignoreAutofillIds = ignoreAutofillIds,
partition = autofillPartition,
uri = URI,
)
val filledItem = FilledItem(
autofillId = autofillId,
@@ -100,4 +108,8 @@ class FilledDataBuilderTest {
// Verify
assertEquals(expected, actual)
}
companion object {
private const val URI: String = "androidapp://com.x8bit.bitwarden"
}
}

View File

@@ -6,11 +6,14 @@ import android.view.autofill.AutofillId
import com.x8bit.bitwarden.data.autofill.model.AutofillPartition
import com.x8bit.bitwarden.data.autofill.model.AutofillRequest
import com.x8bit.bitwarden.data.autofill.model.AutofillView
import com.x8bit.bitwarden.data.autofill.model.ViewNodeTraversalData
import com.x8bit.bitwarden.data.autofill.util.buildUriOrNull
import com.x8bit.bitwarden.data.autofill.util.toAutofillView
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import io.mockk.verify
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
@@ -44,12 +47,15 @@ class AutofillParserTests {
@BeforeEach
fun setup() {
mockkStatic(AssistStructure.ViewNode::toAutofillView)
mockkStatic(List<ViewNodeTraversalData>::buildUriOrNull)
every { any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure) } returns URI
parser = AutofillParserImpl()
}
@AfterEach
fun teardown() {
unmockkStatic(AssistStructure.ViewNode::toAutofillView)
unmockkStatic(List<ViewNodeTraversalData>::buildUriOrNull)
}
@Test
@@ -82,6 +88,9 @@ class AutofillParserTests {
val parentAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
autofillId = parentAutofillId,
isFocused = true,
idPackage = null,
webDomain = null,
webScheme = null,
)
val parentViewNode: AssistStructure.ViewNode = mockk {
every { this@mockk.autofillHints } returns arrayOf(parentAutofillHint)
@@ -99,6 +108,7 @@ class AutofillParserTests {
val expected = AutofillRequest.Fillable(
ignoreAutofillIds = listOf(childAutofillId),
partition = autofillPartition,
uri = URI,
)
every { assistStructure.windowNodeCount } returns 1
every { assistStructure.getWindowNodeAt(0) } returns windowNode
@@ -108,6 +118,9 @@ class AutofillParserTests {
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
}
}
@Test
@@ -117,10 +130,16 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
autofillId = cardAutofillId,
isFocused = true,
idPackage = null,
webDomain = null,
webScheme = null,
)
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
autofillId = loginAutofillId,
isFocused = false,
idPackage = null,
webDomain = null,
webScheme = null,
)
val autofillPartition = AutofillPartition.Card(
views = listOf(cardAutofillView),
@@ -128,6 +147,7 @@ class AutofillParserTests {
val expected = AutofillRequest.Fillable(
ignoreAutofillIds = emptyList(),
partition = autofillPartition,
uri = URI,
)
every { cardViewNode.toAutofillView() } returns cardAutofillView
every { loginViewNode.toAutofillView() } returns loginAutofillView
@@ -137,6 +157,9 @@ class AutofillParserTests {
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
}
}
@Test
@@ -146,10 +169,16 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
autofillId = cardAutofillId,
isFocused = false,
idPackage = null,
webDomain = null,
webScheme = null,
)
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
autofillId = loginAutofillId,
isFocused = true,
idPackage = null,
webDomain = null,
webScheme = null,
)
val autofillPartition = AutofillPartition.Login(
views = listOf(loginAutofillView),
@@ -157,6 +186,7 @@ class AutofillParserTests {
val expected = AutofillRequest.Fillable(
ignoreAutofillIds = emptyList(),
partition = autofillPartition,
uri = URI,
)
every { cardViewNode.toAutofillView() } returns cardAutofillView
every { loginViewNode.toAutofillView() } returns loginAutofillView
@@ -166,6 +196,9 @@ class AutofillParserTests {
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
}
}
@Test
@@ -175,10 +208,16 @@ class AutofillParserTests {
val cardAutofillView: AutofillView.Card = AutofillView.Card.ExpirationMonth(
autofillId = cardAutofillId,
isFocused = true,
idPackage = null,
webDomain = null,
webScheme = null,
)
val loginAutofillView: AutofillView.Login = AutofillView.Login.Username(
autofillId = loginAutofillId,
isFocused = true,
idPackage = null,
webDomain = null,
webScheme = null,
)
val autofillPartition = AutofillPartition.Card(
views = listOf(cardAutofillView),
@@ -186,6 +225,7 @@ class AutofillParserTests {
val expected = AutofillRequest.Fillable(
ignoreAutofillIds = emptyList(),
partition = autofillPartition,
uri = URI,
)
every { cardViewNode.toAutofillView() } returns cardAutofillView
every { loginViewNode.toAutofillView() } returns loginAutofillView
@@ -195,6 +235,9 @@ class AutofillParserTests {
// Verify
assertEquals(expected, actual)
verify(exactly = 1) {
any<List<ViewNodeTraversalData>>().buildUriOrNull(assistStructure)
}
}
/**
@@ -206,4 +249,8 @@ class AutofillParserTests {
every { assistStructure.getWindowNodeAt(0) } returns cardWindowNode
every { assistStructure.getWindowNodeAt(1) } returns loginWindowNode
}
companion object {
private const val URI: String = "androidapp://com.x8bit.bitwarden"
}
}

View File

@@ -16,7 +16,10 @@ class ViewNodeExtensionsTest {
private val viewNode: AssistStructure.ViewNode = mockk {
every { this@mockk.autofillId } returns expectedAutofillId
every { this@mockk.childCount } returns 0
every { this@mockk.idPackage } returns ID_PACKAGE
every { this@mockk.isFocused } returns expectedIsFocused
every { this@mockk.webDomain } returns WEB_DOMAIN
every { this@mockk.webScheme } returns WEB_SCHEME
}
@Test
@@ -25,7 +28,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH
val expected = AutofillView.Card.ExpirationMonth(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -42,7 +48,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
val expected = AutofillView.Card.ExpirationYear(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -59,7 +68,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_NUMBER
val expected = AutofillView.Card.Number(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -76,7 +88,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE
val expected = AutofillView.Card.SecurityCode(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -93,7 +108,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_EMAIL_ADDRESS
val expected = AutofillView.Login.EmailAddress(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -110,7 +128,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_PASSWORD
val expected = AutofillView.Login.Password(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -127,7 +148,10 @@ class ViewNodeExtensionsTest {
val autofillHint = View.AUTOFILL_HINT_USERNAME
val expected = AutofillView.Login.Username(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHint)
@@ -158,7 +182,10 @@ class ViewNodeExtensionsTest {
val autofillHintTwo = View.AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR
val expected = AutofillView.Card.ExpirationYear(
autofillId = expectedAutofillId,
idPackage = ID_PACKAGE,
isFocused = expectedIsFocused,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
every { viewNode.autofillHints } returns arrayOf(autofillHintOne, autofillHintTwo)
@@ -168,4 +195,10 @@ class ViewNodeExtensionsTest {
// Verify
assertEquals(expected, actual)
}
companion object {
private const val ID_PACKAGE: String = "ID_PACKAGE"
private const val WEB_DOMAIN: String = "WEB_DOMAIN"
private const val WEB_SCHEME: String = "WEB_SCHEME"
}
}

View File

@@ -0,0 +1,270 @@
package com.x8bit.bitwarden.data.autofill.util
import android.app.assist.AssistStructure
import com.x8bit.bitwarden.data.autofill.model.AutofillView
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 {
private val windowNode: AssistStructure.WindowNode = mockk()
private val assistStructure: AssistStructure = mockk {
every { this@mockk.windowNodeCount } returns 1
every { this@mockk.getWindowNodeAt(0) } returns windowNode
}
@Test
fun `buildUriOrNull should return URI when contains valid domain and scheme`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = null,
isFocused = false,
webDomain = WEB_DOMAIN,
webScheme = WEB_SCHEME,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "$WEB_SCHEME://$WEB_DOMAIN"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Test
fun `buildUriOrNull should return URI with default scheme when domain valid and scheme null`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = null,
isFocused = false,
webDomain = WEB_DOMAIN,
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "https://$WEB_DOMAIN"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Suppress("MaxLineLength")
@Test
fun `buildUriOrNull should return URI with default scheme when domain valid and scheme empty`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = null,
isFocused = false,
webDomain = WEB_DOMAIN,
webScheme = "",
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "https://$WEB_DOMAIN"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Test
fun `buildUriOrNull should return idPackage URI when domain is null`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = ID_PACKAGE,
isFocused = false,
webDomain = null,
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "androidapp://$ID_PACKAGE"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Test
fun `buildUriOrNull should return idPackage URI when domain is empty`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = ID_PACKAGE,
isFocused = false,
webDomain = null,
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "androidapp://$ID_PACKAGE"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Test
fun `buildUriOrNull should return title URI when domain and idPackage are null`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = null,
isFocused = false,
webDomain = null,
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "androidapp://com.x8bit.bitwarden"
every { windowNode.title } returns "com.x8bit.bitwarden/path.deeper.into.app"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Test
fun `buildUriOrNull should return title URI when domain and idPackage are empty`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = "",
isFocused = false,
webDomain = "",
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
val expected = "androidapp://com.x8bit.bitwarden"
every { windowNode.title } returns "com.x8bit.bitwarden/path.deeper.into.app"
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertEquals(expected, actual)
}
@Test
fun `buildUriOrNull should return null when title, domain, and idPackage are null`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = null,
isFocused = false,
webDomain = null,
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
every { windowNode.title } returns null
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertNull(actual)
}
@Test
fun `buildUriOrNull should return null when title, domain, and idPackage are empty`() {
// Setup
val autofillView = AutofillView.Card.Number(
autofillId = mockk(),
idPackage = "",
isFocused = false,
webDomain = "",
webScheme = null,
)
val viewNodeTraversalData = ViewNodeTraversalData(
autofillViews = listOf(
autofillView,
),
ignoreAutofillIds = emptyList(),
)
every { windowNode.title } returns ""
// Test
val actual = listOf(viewNodeTraversalData).buildUriOrNull(
assistStructure = assistStructure,
)
// Verify
assertNull(actual)
}
companion object {
private const val ID_PACKAGE: String = "com.x8bit.bitwarden"
private const val WEB_DOMAIN: String = "www.google.com"
private const val WEB_SCHEME: String = "https"
}
}