From 2364cbf0ed4044beab57f7bd7f4643a56187909a Mon Sep 17 00:00:00 2001 From: Lucas Kivi <125697099+lucas-livefront@users.noreply.github.com> Date: Sat, 13 Jan 2024 15:51:38 -0600 Subject: [PATCH] BIT-1315: Add dummy data fulfillment (#602) --- .../autofill/builder/FilledDataBuilderImpl.kt | 74 ++++++-- .../data/autofill/di/AutofillModule.kt | 11 +- .../data/autofill/model/AutofillCipher.kt | 41 ++++ .../data/autofill/model/FilledItem.kt | 2 + .../data/autofill/model/FilledPartition.kt | 6 +- .../provider/AutofillCipherProvider.kt | 20 ++ .../provider/AutofillCipherProviderImpl.kt | 56 ++++++ .../autofill/util/AutofillViewExtensions.kt | 17 ++ .../autofill/util/FilledItemExtensions.kt | 3 +- .../util/FilledPartitionExtensions.kt | 3 +- .../builder/FillResponseBuilderTest.kt | 1 + .../autofill/builder/FilledDataBuilderTest.kt | 176 ++++++++++++++++-- .../processor/AutofillCipherProviderTest.kt | 72 +++++++ .../util/AutofillViewExtensionsTest.kt | 62 ++++++ .../autofill/util/FilledItemExtensionsTest.kt | 9 +- .../util/FilledPartitionExtensionsTest.kt | 19 +- 16 files changed, 522 insertions(+), 50 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProvider.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt create mode 100644 app/src/main/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt index 9b5816fc0a..84b38a5b29 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderImpl.kt @@ -1,37 +1,52 @@ package com.x8bit.bitwarden.data.autofill.builder +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher 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.FilledData -import com.x8bit.bitwarden.data.autofill.model.FilledItem import com.x8bit.bitwarden.data.autofill.model.FilledPartition +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider +import com.x8bit.bitwarden.data.autofill.util.buildFilledItemOrNull /** * The default [FilledDataBuilder]. This converts parsed autofill data into filled data that is * ready to be loaded into an autofill response. */ -class FilledDataBuilderImpl : FilledDataBuilder { +class FilledDataBuilderImpl( + private val autofillCipherProvider: AutofillCipherProvider, +) : FilledDataBuilder { override suspend fun build(autofillRequest: AutofillRequest.Fillable): FilledData { // TODO: determine whether or not the vault is locked (BIT-1296) val filledPartitions = when (autofillRequest.partition) { is AutofillPartition.Card -> { - // TODO: perform fulfillment with dummy data (BIT-1315) - listOf( - fillCardPartition( - autofillViews = autofillRequest.partition.views, - ), - ) + autofillCipherProvider + .getCardAutofillCiphers() + .map { autofillCipher -> + fillCardPartition( + autofillCipher = autofillCipher, + autofillViews = autofillRequest.partition.views, + ) + } } is AutofillPartition.Login -> { - // TODO: perform fulfillment with dummy data (BIT-1315) - listOf( - fillLoginPartition( - autofillViews = autofillRequest.partition.views, - ), - ) + autofillRequest + .uri + ?.let { nonNullUri -> + autofillCipherProvider + .getLoginAutofillCiphers( + uri = nonNullUri, + ) + .map { autofillCipher -> + fillLoginPartition( + autofillCipher = autofillCipher, + autofillViews = autofillRequest.partition.views, + ) + } + } + ?: emptyList() } } @@ -42,37 +57,56 @@ class FilledDataBuilderImpl : FilledDataBuilder { } /** - * Construct a [FilledPartition] by fulfilling the card [autofillViews] with data. + * Construct a [FilledPartition] by fulfilling the card [autofillViews] with data from the + * card [autofillCipher]. */ private fun fillCardPartition( + autofillCipher: AutofillCipher.Card, autofillViews: List, ): FilledPartition { val filledItems = autofillViews .map { autofillView -> - FilledItem( - autofillId = autofillView.data.autofillId, + val value = when (autofillView) { + is AutofillView.Card.ExpirationMonth -> autofillCipher.expirationMonth + is AutofillView.Card.ExpirationYear -> autofillCipher.expirationYear + is AutofillView.Card.Number -> autofillCipher.number + is AutofillView.Card.SecurityCode -> autofillCipher.code + } + autofillView.buildFilledItemOrNull( + value = value, ) } return FilledPartition( + autofillCipher = autofillCipher, filledItems = filledItems, ) } /** - * Construct a [FilledPartition] by fulfilling the login [autofillViews] with data. + * Construct a [FilledPartition] by fulfilling the login [autofillViews] with data from the + * login [autofillCipher]. */ private fun fillLoginPartition( + autofillCipher: AutofillCipher.Login, autofillViews: List, ): FilledPartition { val filledItems = autofillViews .map { autofillView -> - FilledItem( - autofillId = autofillView.data.autofillId, + val value = when (autofillView) { + is AutofillView.Login.EmailAddress, + is AutofillView.Login.Username, + -> autofillCipher.username + + is AutofillView.Login.Password -> autofillCipher.password + } + autofillView.buildFilledItemOrNull( + value = value, ) } return FilledPartition( + autofillCipher = autofillCipher, filledItems = filledItems, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt index 095bea1509..8f9db6a924 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/di/AutofillModule.kt @@ -8,6 +8,8 @@ import com.x8bit.bitwarden.data.autofill.parser.AutofillParser import com.x8bit.bitwarden.data.autofill.parser.AutofillParserImpl import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessor import com.x8bit.bitwarden.data.autofill.processor.AutofillProcessorImpl +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProviderImpl import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager import dagger.Module import dagger.Provides @@ -23,6 +25,9 @@ object AutofillModule { @Provides fun providesAutofillParser(): AutofillParser = AutofillParserImpl() + @Provides + fun providesAutofillCipherProvider(): AutofillCipherProvider = AutofillCipherProviderImpl() + @Provides fun providesAutofillProcessor( dispatcherManager: DispatcherManager, @@ -38,7 +43,11 @@ object AutofillModule { ) @Provides - fun providesFillDataBuilder(): FilledDataBuilder = FilledDataBuilderImpl() + fun providesFillDataBuilder( + autofillCipherProvider: AutofillCipherProvider, + ): FilledDataBuilder = FilledDataBuilderImpl( + autofillCipherProvider = autofillCipherProvider, + ) @Provides fun providesFillResponseBuilder(): FillResponseBuilder = FillResponseBuilderImpl() diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt new file mode 100644 index 0000000000..b5a614a007 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/AutofillCipher.kt @@ -0,0 +1,41 @@ +package com.x8bit.bitwarden.data.autofill.model + +/** + * A paired down model of the CipherView for use within the autofill feature. + */ +sealed class AutofillCipher { + /** + * The name of the cipher. + */ + abstract val name: String + + /** + * The subtitle for giving additional context to the cipher. + */ + abstract val subtitle: String + + /** + * The card [AutofillCipher] model. This contains all of the data for building fulfilling a card + * partition. + */ + data class Card( + override val name: String, + override val subtitle: String, + val cardholderName: String, + val code: String, + val expirationMonth: String, + val expirationYear: String, + val number: String, + ) : AutofillCipher() + + /** + * The card [AutofillCipher] model. This contains all of the data for building fulfilling a + * login partition. + */ + data class Login( + override val name: String, + override val subtitle: String, + val password: String, + val username: String, + ) : AutofillCipher() +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledItem.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledItem.kt index d8685b3791..ede5c3f64e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledItem.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledItem.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.data.autofill.model import android.view.autofill.AutofillId +import android.view.autofill.AutofillValue /** * A fulfilled autofill view. This contains everything required to build the autofill UI @@ -8,4 +9,5 @@ import android.view.autofill.AutofillId */ data class FilledItem( val autofillId: AutofillId, + val value: AutofillValue, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledPartition.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledPartition.kt index ee41403f32..04dbbae00e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledPartition.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/model/FilledPartition.kt @@ -1,11 +1,13 @@ package com.x8bit.bitwarden.data.autofill.model /** - * All of the data required to build a `Dataset` for fulfilling a partition of data based on a - * cipher. + * All of the data required to build a `Dataset` for fulfilling a partition of data based on an + * [AutofillCipher]. * + * @param autofillCipher The cipher used to fulfill these [filledItems]. * @param filledItems A filled copy of each view from this partition. */ data class FilledPartition( + val autofillCipher: AutofillCipher, val filledItems: List, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProvider.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProvider.kt new file mode 100644 index 0000000000..1241dec012 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProvider.kt @@ -0,0 +1,20 @@ +package com.x8bit.bitwarden.data.autofill.provider + +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher + +/** + * A service for getting [AutofillCipher]s. + */ +interface AutofillCipherProvider { + /** + * Get all [AutofillCipher.Card]s for the current user. + */ + suspend fun getCardAutofillCiphers(): List + + /** + * Get all [AutofillCipher.Login]s for the current user. + */ + suspend fun getLoginAutofillCiphers( + uri: String, + ): List +} diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt new file mode 100644 index 0000000000..387f15da30 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/provider/AutofillCipherProviderImpl.kt @@ -0,0 +1,56 @@ +package com.x8bit.bitwarden.data.autofill.provider + +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher + +/** + * The default [AutofillCipherProvider] implementation. This service is used for getting currrent + * [AutofillCipher]s. + */ +class AutofillCipherProviderImpl : AutofillCipherProvider { + override suspend fun getCardAutofillCiphers(): List { + // TODO: fulfill with real ciphers (BIT-1294) + return cardCiphers + } + + override suspend fun getLoginAutofillCiphers( + uri: String, + ): List { + // TODO: fulfill with real ciphers (BIT-1294) + return loginCiphers + } +} + +private val cardCiphers = listOf( + AutofillCipher.Card( + cardholderName = "John", + code = "123", + expirationMonth = "January", + expirationYear = "1999", + name = "John", + number = "1234567890", + subtitle = "123...", + ), + AutofillCipher.Card( + cardholderName = "Doe", + code = "456", + expirationMonth = "December", + expirationYear = "2024", + name = "Doe", + number = "0987654321", + subtitle = "098...", + ), +) +private val loginCiphers = listOf( + AutofillCipher.Login( + name = "Bitwarden1", + password = "password123", + subtitle = "John-Bitwarden", + username = "John-Bitwarden", + ), + AutofillCipher.Login( + name = "Bitwarden2", + password = "password123", + subtitle = "Doe-Bitwarden", + username = "Doe-Bitwarden", + ), +) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt new file mode 100644 index 0000000000..c9bedd7bb0 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensions.kt @@ -0,0 +1,17 @@ +package com.x8bit.bitwarden.data.autofill.util + +import android.view.autofill.AutofillValue +import com.x8bit.bitwarden.data.autofill.model.AutofillView +import com.x8bit.bitwarden.data.autofill.model.FilledItem + +/** + * Convert this [AutofillView] into a [FilledItem]. + */ +fun AutofillView.buildFilledItemOrNull( + value: String, +): FilledItem = + // TODO: handle other autofill types (BIT-1457) + FilledItem( + autofillId = data.autofillId, + value = AutofillValue.forText(value), + ) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensions.kt index 62399acd16..15d5a91d5a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensions.kt @@ -19,6 +19,7 @@ fun FilledItem.applyToDatasetPostTiramisu( datasetBuilder.setField( autofillId, Field.Builder() + .setValue(value) .setPresentations(presentations) .build(), ) @@ -35,7 +36,7 @@ fun FilledItem.applyToDatasetPreTiramisu( ) { datasetBuilder.setValue( autofillId, - null, + value, remoteViews, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensions.kt index 359f400c1e..86bf04014c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensions.kt @@ -5,7 +5,6 @@ import android.os.Build import android.service.autofill.Dataset import android.service.autofill.Presentations import android.widget.RemoteViews -import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo import com.x8bit.bitwarden.data.autofill.model.FilledPartition import com.x8bit.bitwarden.ui.autofill.buildAutofillRemoteViews @@ -19,7 +18,7 @@ fun FilledPartition.buildDataset( ): Dataset { val remoteViewsPlaceholder = buildAutofillRemoteViews( packageName = autofillAppInfo.packageName, - title = autofillAppInfo.context.resources.getString(R.string.app_name), + title = autofillCipher.name, ) val datasetBuilder = Dataset.Builder() diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt index 2e35c9eed8..402f8fb129 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FillResponseBuilderTest.kt @@ -74,6 +74,7 @@ class FillResponseBuilderTest { fun `build should return null when filledPartitions contains no views`() { // Test val filledPartitions = FilledPartition( + autofillCipher = mockk(), filledItems = emptyList(), ) val filledData = FilledData( diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt index de4f5bece9..2c62b49c33 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/builder/FilledDataBuilderTest.kt @@ -1,14 +1,25 @@ package com.x8bit.bitwarden.data.autofill.builder import android.view.autofill.AutofillId +import android.view.autofill.AutofillValue +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher 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.FilledData import com.x8bit.bitwarden.data.autofill.model.FilledItem 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 io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.every import io.mockk.mockk +import io.mockk.mockkStatic +import io.mockk.unmockkStatic +import io.mockk.verify import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -16,6 +27,8 @@ import org.junit.jupiter.api.Test class FilledDataBuilderTest { private lateinit var filledDataBuilder: FilledDataBuilder + private val autofillCipherProvider: AutofillCipherProvider = mockk() + private val autofillId: AutofillId = mockk() private val autofillViewData = AutofillView.Data( autofillId = autofillId, @@ -27,17 +40,45 @@ class FilledDataBuilderTest { @BeforeEach fun setup() { - filledDataBuilder = FilledDataBuilderImpl() + mockkStatic(AutofillValue::forText) + mockkStatic(AutofillView::buildFilledItemOrNull) + filledDataBuilder = FilledDataBuilderImpl( + autofillCipherProvider = autofillCipherProvider, + ) + } + + @AfterEach + fun teardown() { + unmockkStatic(AutofillValue::forText) + unmockkStatic(AutofillView::buildFilledItemOrNull) } @Test fun `build should return filled data and ignored AutofillIds when Login`() = runTest { // Setup - val autofillView = AutofillView.Login.Username( + val password = "Password" + val username = "johnDoe" + val autofillCipher = AutofillCipher.Login( + name = "Cipher One", + password = password, + username = username, + subtitle = "Subtitle", + ) + val autofillViewEmail = AutofillView.Login.EmailAddress( + data = autofillViewData, + ) + val autofillViewPassword = AutofillView.Login.Password( + data = autofillViewData, + ) + val autofillViewUsername = AutofillView.Login.Username( data = autofillViewData, ) val autofillPartition = AutofillPartition.Login( - views = listOf(autofillView), + views = listOf( + autofillViewEmail, + autofillViewPassword, + autofillViewUsername, + ), ) val ignoreAutofillIds: List = mockk() val autofillRequest = AutofillRequest.Fillable( @@ -45,12 +86,15 @@ class FilledDataBuilderTest { partition = autofillPartition, uri = URI, ) - val filledItem = FilledItem( - autofillId = autofillId, - ) + val filledItemEmail: FilledItem = mockk() + val filledItemPassword: FilledItem = mockk() + val filledItemUsername: FilledItem = mockk() val filledPartition = FilledPartition( + autofillCipher = autofillCipher, filledItems = listOf( - filledItem, + filledItemEmail, + filledItemPassword, + filledItemUsername, ), ) val expected = FilledData( @@ -59,6 +103,14 @@ class FilledDataBuilderTest { ), ignoreAutofillIds = ignoreAutofillIds, ) + coEvery { + autofillCipherProvider.getLoginAutofillCiphers( + uri = URI, + ) + } returns listOf(autofillCipher) + every { autofillViewEmail.buildFilledItemOrNull(username) } returns filledItemEmail + every { autofillViewPassword.buildFilledItemOrNull(password) } returns filledItemPassword + every { autofillViewUsername.buildFilledItemOrNull(username) } returns filledItemUsername // Test val actual = filledDataBuilder.build( @@ -67,16 +119,93 @@ class FilledDataBuilderTest { // Verify assertEquals(expected, actual) + coVerify(exactly = 1) { + autofillCipherProvider.getLoginAutofillCiphers( + uri = URI, + ) + } + verify(exactly = 1) { + autofillViewEmail.buildFilledItemOrNull(username) + autofillViewPassword.buildFilledItemOrNull(password) + autofillViewUsername.buildFilledItemOrNull(username) + } } + @Test + fun `build should return no partitions and ignored AutofillIds when Login and no URI`() = + runTest { + // Setup + val autofillViewEmail = AutofillView.Login.EmailAddress( + data = autofillViewData, + ) + val autofillViewPassword = AutofillView.Login.Password( + data = autofillViewData, + ) + val autofillViewUsername = AutofillView.Login.Username( + data = autofillViewData, + ) + val autofillPartition = AutofillPartition.Login( + views = listOf( + autofillViewEmail, + autofillViewPassword, + autofillViewUsername, + ), + ) + val ignoreAutofillIds: List = mockk() + val autofillRequest = AutofillRequest.Fillable( + ignoreAutofillIds = ignoreAutofillIds, + partition = autofillPartition, + uri = null, + ) + val expected = FilledData( + filledPartitions = emptyList(), + ignoreAutofillIds = ignoreAutofillIds, + ) + + // Test + val actual = filledDataBuilder.build( + autofillRequest = autofillRequest, + ) + + // Verify + assertEquals(expected, actual) + } + @Test fun `build should return filled data and ignored AutofillIds when Card`() = runTest { // Setup - val autofillView = AutofillView.Card.Number( + val code = "123" + val expirationMonth = "January" + val expirationYear = "1999" + val number = "1234567890" + val autofillCipher = AutofillCipher.Card( + cardholderName = "John", + code = code, + expirationMonth = expirationMonth, + expirationYear = expirationYear, + name = "Cipher One", + number = number, + subtitle = "Subtitle", + ) + val autofillViewCode = AutofillView.Card.SecurityCode( + data = autofillViewData, + ) + val autofillViewExpirationMonth = AutofillView.Card.ExpirationMonth( + data = autofillViewData, + ) + val autofillViewExpirationYear = AutofillView.Card.ExpirationYear( + data = autofillViewData, + ) + val autofillViewNumber = AutofillView.Card.Number( data = autofillViewData, ) val autofillPartition = AutofillPartition.Card( - views = listOf(autofillView), + views = listOf( + autofillViewCode, + autofillViewExpirationMonth, + autofillViewExpirationYear, + autofillViewNumber, + ), ) val ignoreAutofillIds: List = mockk() val autofillRequest = AutofillRequest.Fillable( @@ -84,12 +213,17 @@ class FilledDataBuilderTest { partition = autofillPartition, uri = URI, ) - val filledItem = FilledItem( - autofillId = autofillId, - ) + val filledItemCode: FilledItem = mockk() + val filledItemExpirationMonth: FilledItem = mockk() + val filledItemExpirationYear: FilledItem = mockk() + val filledItemNumber: FilledItem = mockk() val filledPartition = FilledPartition( + autofillCipher = autofillCipher, filledItems = listOf( - filledItem, + filledItemCode, + filledItemExpirationMonth, + filledItemExpirationYear, + filledItemNumber, ), ) val expected = FilledData( @@ -98,6 +232,15 @@ class FilledDataBuilderTest { ), ignoreAutofillIds = ignoreAutofillIds, ) + coEvery { autofillCipherProvider.getCardAutofillCiphers() } returns listOf(autofillCipher) + every { autofillViewCode.buildFilledItemOrNull(code) } returns filledItemCode + every { + autofillViewExpirationMonth.buildFilledItemOrNull(expirationMonth) + } returns filledItemExpirationMonth + every { + autofillViewExpirationYear.buildFilledItemOrNull(expirationYear) + } returns filledItemExpirationYear + every { autofillViewNumber.buildFilledItemOrNull(number) } returns filledItemNumber // Test val actual = filledDataBuilder.build( @@ -106,6 +249,13 @@ class FilledDataBuilderTest { // Verify assertEquals(expected, actual) + coVerify(exactly = 1) { + autofillCipherProvider.getCardAutofillCiphers() + autofillViewCode.buildFilledItemOrNull(code) + autofillViewExpirationMonth.buildFilledItemOrNull(expirationMonth) + autofillViewExpirationYear.buildFilledItemOrNull(expirationYear) + autofillViewNumber.buildFilledItemOrNull(number) + } } companion object { diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt new file mode 100644 index 0000000000..5f11dc8014 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/processor/AutofillCipherProviderTest.kt @@ -0,0 +1,72 @@ +package com.x8bit.bitwarden.data.autofill.processor + +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProvider +import com.x8bit.bitwarden.data.autofill.provider.AutofillCipherProviderImpl +import kotlinx.coroutines.test.runTest +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class AutofillCipherProviderTest { + private lateinit var autofillCipherProvider: AutofillCipherProvider + + @BeforeEach + fun setup() { + autofillCipherProvider = AutofillCipherProviderImpl() + } + + @Test + fun `getCardAutofillCiphers should return default list of card ciphers`() = runTest { + // Test & Verify + val actual = autofillCipherProvider.getCardAutofillCiphers() + + assertEquals(CARD_CIPHERS, actual) + } + + @Test + fun `getLoginAutofillCiphers should return default list of login ciphers`() = runTest { + // Test & Verify + val actual = autofillCipherProvider.getLoginAutofillCiphers( + uri = URI, + ) + + assertEquals(LOGIN_CIPHERS, actual) + } +} + +private val CARD_CIPHERS = listOf( + AutofillCipher.Card( + cardholderName = "John", + code = "123", + expirationMonth = "January", + expirationYear = "1999", + name = "John", + number = "1234567890", + subtitle = "123...", + ), + AutofillCipher.Card( + cardholderName = "Doe", + code = "456", + expirationMonth = "December", + expirationYear = "2024", + name = "Doe", + number = "0987654321", + subtitle = "098...", + ), +) +private val LOGIN_CIPHERS = listOf( + AutofillCipher.Login( + name = "Bitwarden1", + password = "password123", + subtitle = "John-Bitwarden", + username = "John-Bitwarden", + ), + AutofillCipher.Login( + name = "Bitwarden2", + password = "password123", + subtitle = "Doe-Bitwarden", + username = "Doe-Bitwarden", + ), +) +private const val URI: String = "androidapp://com.x8bit.bitwarden" diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt new file mode 100644 index 0000000000..b0bd075834 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/AutofillViewExtensionsTest.kt @@ -0,0 +1,62 @@ +package com.x8bit.bitwarden.data.autofill.util + +import android.view.autofill.AutofillId +import android.view.autofill.AutofillValue +import com.x8bit.bitwarden.data.autofill.model.AutofillView +import com.x8bit.bitwarden.data.autofill.model.FilledItem +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 +import org.junit.jupiter.api.Test + +class AutofillViewExtensionsTest { + private val autofillId: AutofillId = mockk() + private val autofillValue: AutofillValue = mockk() + private val autofillViewData = AutofillView.Data( + autofillId = autofillId, + idPackage = null, + isFocused = false, + webDomain = null, + webScheme = null, + ) + + @BeforeEach + fun setup() { + mockkStatic(AutofillValue::forText) + } + + @AfterEach + fun teardown() { + unmockkStatic(AutofillValue::forText) + } + + @Test + fun `buildFilledItem returns AutofillValue`() { + // Setup + val value = "2002421451023587L" + val autofillView = AutofillView.Card.Number( + data = autofillViewData, + ) + val expected = FilledItem( + autofillId = autofillId, + value = autofillValue, + ) + every { AutofillValue.forText(value) } returns autofillValue + + // Test + val actual = autofillView.buildFilledItemOrNull( + value = value, + ) + + // Verify + assertEquals(expected, actual) + verify(exactly = 1) { + AutofillValue.forText(value) + } + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensionsTest.kt index 63759c5bbe..66d380a472 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledItemExtensionsTest.kt @@ -4,6 +4,7 @@ import android.service.autofill.Dataset import android.service.autofill.Field import android.service.autofill.Presentations import android.view.autofill.AutofillId +import android.view.autofill.AutofillValue import android.widget.RemoteViews import com.x8bit.bitwarden.data.autofill.model.FilledItem import com.x8bit.bitwarden.data.util.mockBuilder @@ -18,10 +19,12 @@ import org.junit.jupiter.api.Test class FilledItemExtensionsTest { private val autofillId: AutofillId = mockk() + private val autofillValue: AutofillValue = mockk() private val datasetBuilder: Dataset.Builder = mockk() private val field: Field = mockk() private val filledItem = FilledItem( autofillId = autofillId, + value = autofillValue, ) private val presentations: Presentations = mockk() private val remoteViews: RemoteViews = mockk() @@ -44,7 +47,7 @@ class FilledItemExtensionsTest { every { datasetBuilder.setValue( autofillId, - null, + autofillValue, remoteViews, ) } returns datasetBuilder @@ -59,7 +62,7 @@ class FilledItemExtensionsTest { verify(exactly = 1) { datasetBuilder.setValue( autofillId, - null, + autofillValue, remoteViews, ) } @@ -68,6 +71,7 @@ class FilledItemExtensionsTest { @Test fun `applyToDatasetPostTiramisu should use setField to set presentations`() { // Setup + mockBuilder { it.setValue(autofillValue) } mockBuilder { it.setPresentations(presentations) } every { datasetBuilder.setField( @@ -84,6 +88,7 @@ class FilledItemExtensionsTest { // Verify verify(exactly = 1) { + anyConstructed().setValue(autofillValue) anyConstructed().setPresentations(presentations) datasetBuilder.setField( autofillId, diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensionsTest.kt index cc9a7d08a3..54eb4d5bea 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/FilledPartitionExtensionsTest.kt @@ -5,8 +5,8 @@ import android.content.res.Resources import android.service.autofill.Dataset import android.service.autofill.Presentations import android.widget.RemoteViews -import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.autofill.model.AutofillAppInfo +import com.x8bit.bitwarden.data.autofill.model.AutofillCipher import com.x8bit.bitwarden.data.autofill.model.FilledItem import com.x8bit.bitwarden.data.autofill.model.FilledPartition import com.x8bit.bitwarden.data.util.mockBuilder @@ -26,6 +26,9 @@ import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test class FilledPartitionExtensionsTest { + private val autofillCipher: AutofillCipher = mockk { + every { this@mockk.name } returns CIPHER_NAME + } private val res: Resources = mockk() private val context: Context = mockk { every { this@mockk.resources } returns res @@ -33,6 +36,7 @@ class FilledPartitionExtensionsTest { private val dataset: Dataset = mockk() private val filledItem: FilledItem = mockk() private val filledPartition = FilledPartition( + autofillCipher = autofillCipher, filledItems = listOf( filledItem, ), @@ -67,12 +71,10 @@ class FilledPartitionExtensionsTest { packageName = PACKAGE_NAME, sdkInt = 34, ) - val title = "Bitwarden" - every { res.getString(R.string.app_name) } returns title every { buildAutofillRemoteViews( packageName = PACKAGE_NAME, - title = title, + title = CIPHER_NAME, ) } returns remoteViews mockBuilder { it.setMenuPresentation(remoteViews) } @@ -94,7 +96,7 @@ class FilledPartitionExtensionsTest { verify(exactly = 1) { buildAutofillRemoteViews( packageName = PACKAGE_NAME, - title = title, + title = CIPHER_NAME, ) anyConstructed().setMenuPresentation(remoteViews) anyConstructed().build() @@ -114,12 +116,10 @@ class FilledPartitionExtensionsTest { packageName = PACKAGE_NAME, sdkInt = 18, ) - val title = "Bitwarden" - every { res.getString(R.string.app_name) } returns title every { buildAutofillRemoteViews( packageName = PACKAGE_NAME, - title = title, + title = CIPHER_NAME, ) } returns remoteViews every { @@ -139,7 +139,7 @@ class FilledPartitionExtensionsTest { verify(exactly = 1) { buildAutofillRemoteViews( packageName = PACKAGE_NAME, - title = title, + title = CIPHER_NAME, ) filledItem.applyToDatasetPreTiramisu( datasetBuilder = any(), @@ -150,6 +150,7 @@ class FilledPartitionExtensionsTest { } companion object { + private const val CIPHER_NAME: String = "Autofill Cipher" private const val PACKAGE_NAME: String = "com.x8bit.bitwarden" } }