mirror of
https://github.com/bitwarden/android.git
synced 2024-12-18 07:11:51 +03:00
PM-13019: Add special circumstance to navigate to the vault listing UI for TOTP code (#4033)
This commit is contained in:
parent
8d578a9b57
commit
c4467f0cba
10 changed files with 201 additions and 5 deletions
|
@ -32,6 +32,7 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||||
|
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
@ -232,6 +233,7 @@ class MainViewModel @Inject constructor(
|
||||||
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
val autofillSaveItem = intent.getAutofillSaveItemOrNull()
|
||||||
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
val autofillSelectionData = intent.getAutofillSelectionDataOrNull()
|
||||||
val shareData = intentManager.getShareDataFromIntent(intent)
|
val shareData = intentManager.getShareDataFromIntent(intent)
|
||||||
|
val totpData = intent.getTotpDataOrNull()
|
||||||
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
val hasGeneratorShortcut = intent.isPasswordGeneratorShortcut
|
||||||
val hasVaultShortcut = intent.isMyVaultShortcut
|
val hasVaultShortcut = intent.isMyVaultShortcut
|
||||||
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
|
val fido2CredentialRequestData = intent.getFido2CredentialRequestOrNull()
|
||||||
|
@ -270,6 +272,11 @@ class MainViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totpData != null -> {
|
||||||
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData)
|
||||||
|
}
|
||||||
|
|
||||||
shareData != null -> {
|
shareData != null -> {
|
||||||
specialCircumstanceManager.specialCircumstance =
|
specialCircumstanceManager.specialCircumstance =
|
||||||
SpecialCircumstance.ShareNewSend(
|
SpecialCircumstance.ShareNewSend(
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,6 +15,14 @@ import kotlinx.parcelize.Parcelize
|
||||||
* of navigation that is counter to what otherwise may happen based on the state of the app.
|
* of navigation that is counter to what otherwise may happen based on the state of the app.
|
||||||
*/
|
*/
|
||||||
sealed class SpecialCircumstance : Parcelable {
|
sealed class SpecialCircumstance : Parcelable {
|
||||||
|
/**
|
||||||
|
* The app was launched in order to add a new TOTP to a cipher.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class AddTotpLoginItem(
|
||||||
|
val data: TotpData,
|
||||||
|
) : SpecialCircumstance()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The app was launched in order to create/share a new Send using the given [data].
|
* The app was launched in order to create/share a new Send using the given [data].
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,6 +6,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.Fido2GetCredentialsRequest
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns [AutofillSaveItem] when contained in the given [SpecialCircumstance].
|
* Returns [AutofillSaveItem] when contained in the given [SpecialCircumstance].
|
||||||
|
@ -51,3 +52,12 @@ fun SpecialCircumstance.toFido2GetCredentialsRequestOrNull(): Fido2GetCredential
|
||||||
is SpecialCircumstance.Fido2GetCredentials -> this.fido2GetCredentialsRequest
|
is SpecialCircumstance.Fido2GetCredentials -> this.fido2GetCredentialsRequest
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the [TotpData] when contained in the given [SpecialCircumstance].
|
||||||
|
*/
|
||||||
|
fun SpecialCircumstance.toTotpDataOrNull(): TotpData? =
|
||||||
|
when (this) {
|
||||||
|
is SpecialCircumstance.AddTotpLoginItem -> this.data
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ fun RootNavScreen(
|
||||||
is RootNavState.VaultUnlockedForAutofillSave,
|
is RootNavState.VaultUnlockedForAutofillSave,
|
||||||
is RootNavState.VaultUnlockedForAutofillSelection,
|
is RootNavState.VaultUnlockedForAutofillSelection,
|
||||||
is RootNavState.VaultUnlockedForNewSend,
|
is RootNavState.VaultUnlockedForNewSend,
|
||||||
|
is RootNavState.VaultUnlockedForNewTotp,
|
||||||
is RootNavState.VaultUnlockedForAuthRequest,
|
is RootNavState.VaultUnlockedForAuthRequest,
|
||||||
is RootNavState.VaultUnlockedForFido2Save,
|
is RootNavState.VaultUnlockedForFido2Save,
|
||||||
is RootNavState.VaultUnlockedForFido2Assertion,
|
is RootNavState.VaultUnlockedForFido2Assertion,
|
||||||
|
@ -197,6 +198,14 @@ fun RootNavScreen(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is RootNavState.VaultUnlockedForNewTotp -> {
|
||||||
|
navController.navigateToVaultUnlock(rootNavOptions)
|
||||||
|
navController.navigateToVaultItemListingAsRoot(
|
||||||
|
vaultItemListingType = VaultItemListingType.Login,
|
||||||
|
navOptions = rootNavOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is RootNavState.VaultUnlockedForAutofillSave -> {
|
is RootNavState.VaultUnlockedForAutofillSave -> {
|
||||||
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
navController.navigateToVaultUnlockedGraph(rootNavOptions)
|
||||||
navController.navigateToVaultAddEdit(
|
navController.navigateToVaultAddEdit(
|
||||||
|
|
|
@ -117,6 +117,12 @@ class RootNavViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is SpecialCircumstance.AddTotpLoginItem -> {
|
||||||
|
RootNavState.VaultUnlockedForNewTotp(
|
||||||
|
activeUserId = userState.activeAccount.userId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
is SpecialCircumstance.ShareNewSend -> RootNavState.VaultUnlockedForNewSend
|
is SpecialCircumstance.ShareNewSend -> RootNavState.VaultUnlockedForNewSend
|
||||||
|
|
||||||
is SpecialCircumstance.PasswordlessRequest -> {
|
is SpecialCircumstance.PasswordlessRequest -> {
|
||||||
|
@ -305,6 +311,14 @@ sealed class RootNavState : Parcelable {
|
||||||
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest,
|
val fido2GetCredentialsRequest: Fido2GetCredentialsRequest,
|
||||||
) : RootNavState()
|
) : RootNavState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App should show the new verification codes listing screen for an unlocked user.
|
||||||
|
*/
|
||||||
|
@Parcelize
|
||||||
|
data class VaultUnlockedForNewTotp(
|
||||||
|
val activeUserId: String,
|
||||||
|
) : RootNavState()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App should show the new send screen for an unlocked user.
|
* App should show the new send screen for an unlocked user.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -50,6 +50,8 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppThem
|
||||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isMyVaultShortcut
|
||||||
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
import com.x8bit.bitwarden.ui.platform.util.isPasswordGeneratorShortcut
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
|
import com.x8bit.bitwarden.ui.vault.util.getTotpDataOrNull
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.just
|
import io.mockk.just
|
||||||
|
@ -119,6 +121,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setup() {
|
fun setup() {
|
||||||
mockkStatic(
|
mockkStatic(
|
||||||
|
Intent::getTotpDataOrNull,
|
||||||
Intent::getPasswordlessRequestDataIntentOrNull,
|
Intent::getPasswordlessRequestDataIntentOrNull,
|
||||||
Intent::getAutofillSaveItemOrNull,
|
Intent::getAutofillSaveItemOrNull,
|
||||||
Intent::getAutofillSelectionDataOrNull,
|
Intent::getAutofillSelectionDataOrNull,
|
||||||
|
@ -134,6 +137,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
@AfterEach
|
@AfterEach
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
unmockkStatic(
|
unmockkStatic(
|
||||||
|
Intent::getTotpDataOrNull,
|
||||||
Intent::getPasswordlessRequestDataIntentOrNull,
|
Intent::getPasswordlessRequestDataIntentOrNull,
|
||||||
Intent::getAutofillSaveItemOrNull,
|
Intent::getAutofillSaveItemOrNull,
|
||||||
Intent::getAutofillSelectionDataOrNull,
|
Intent::getAutofillSelectionDataOrNull,
|
||||||
|
@ -294,12 +298,35 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on ReceiveFirstIntent with TOTP data should set the special circumstance to AddTotpLoginItem`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val mockIntent = mockk<Intent>()
|
||||||
|
val totpData = mockk<TotpData>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns totpData
|
||||||
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||||
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||||
|
every { mockIntent.isMyVaultShortcut } returns false
|
||||||
|
every { mockIntent.isPasswordGeneratorShortcut } returns false
|
||||||
|
|
||||||
|
viewModel.trySendAction(MainAction.ReceiveFirstIntent(intent = mockIntent))
|
||||||
|
assertEquals(
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData),
|
||||||
|
specialCircumstanceManager.specialCircumstance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
|
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent>()
|
val mockIntent = mockk<Intent>()
|
||||||
val shareData = mockk<IntentManager.ShareData>()
|
val shareData = mockk<IntentManager.ShareData>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -328,6 +355,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent>()
|
val mockIntent = mockk<Intent>()
|
||||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||||
|
@ -359,6 +387,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every { verificationToken } returns "token"
|
every { verificationToken } returns "token"
|
||||||
}
|
}
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||||
|
@ -394,6 +423,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every { verificationToken } returns "token"
|
every { verificationToken } returns "token"
|
||||||
}
|
}
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||||
|
@ -431,6 +461,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every { verificationToken } returns token
|
every { verificationToken } returns token
|
||||||
}
|
}
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||||
|
@ -470,6 +501,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every { verificationToken } returns token
|
every { verificationToken } returns token
|
||||||
}
|
}
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||||
|
@ -511,6 +543,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every { verificationToken } returns token
|
every { verificationToken } returns token
|
||||||
}
|
}
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
every { getCompleteRegistrationDataIntentOrNull() } returns completeRegistrationData
|
||||||
|
@ -548,6 +581,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent>()
|
val mockIntent = mockk<Intent>()
|
||||||
val autofillSaveItem = mockk<AutofillSaveItem>()
|
val autofillSaveItem = mockk<AutofillSaveItem>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
||||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -578,6 +612,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every {
|
every {
|
||||||
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
||||||
} returns passwordlessRequestData
|
} returns passwordlessRequestData
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||||
|
@ -663,6 +698,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
origin = "mockOrigin",
|
origin = "mockOrigin",
|
||||||
)
|
)
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -701,6 +737,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
|
@ -773,6 +810,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent>()
|
val mockIntent = mockk<Intent>()
|
||||||
val shareData = mockk<IntentManager.ShareData>()
|
val shareData = mockk<IntentManager.ShareData>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -795,12 +833,35 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `on ReceiveNewIntent with TOTP data should set the special circumstance to AddTotpLoginItem`() {
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
val mockIntent = mockk<Intent>()
|
||||||
|
val totpData = mockk<TotpData>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns totpData
|
||||||
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||||
|
every { intentManager.getShareDataFromIntent(mockIntent) } returns null
|
||||||
|
every { mockIntent.isMyVaultShortcut } returns false
|
||||||
|
every { mockIntent.isPasswordGeneratorShortcut } returns false
|
||||||
|
|
||||||
|
viewModel.trySendAction(MainAction.ReceiveNewIntent(intent = mockIntent))
|
||||||
|
assertEquals(
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData),
|
||||||
|
specialCircumstanceManager.specialCircumstance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
fun `on ReceiveNewIntent with autofill data should set the special circumstance to AutofillSelection`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent>()
|
val mockIntent = mockk<Intent>()
|
||||||
val autofillSelectionData = mockk<AutofillSelectionData>()
|
val autofillSelectionData = mockk<AutofillSelectionData>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||||
|
@ -829,6 +890,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent>()
|
val mockIntent = mockk<Intent>()
|
||||||
val autofillSaveItem = mockk<AutofillSaveItem>()
|
val autofillSaveItem = mockk<AutofillSaveItem>()
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
every { mockIntent.getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
every { mockIntent.getAutofillSaveItemOrNull() } returns autofillSaveItem
|
||||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -859,6 +921,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
every {
|
every {
|
||||||
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
mockIntent.getPasswordlessRequestDataIntentOrNull()
|
||||||
} returns passwordlessRequestData
|
} returns passwordlessRequestData
|
||||||
|
every { mockIntent.getTotpDataOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
every { mockIntent.getAutofillSaveItemOrNull() } returns null
|
||||||
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
every { mockIntent.getAutofillSelectionDataOrNull() } returns null
|
||||||
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
every { mockIntent.getCompleteRegistrationDataIntentOrNull() } returns null
|
||||||
|
@ -885,6 +948,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
fun `on ReceiveNewIntent with a Vault deeplink data should set the special circumstance to VaultShortcut`() {
|
fun `on ReceiveNewIntent with a Vault deeplink data should set the special circumstance to VaultShortcut`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -910,6 +974,7 @@ class MainViewModelTest : BaseViewModelTest() {
|
||||||
fun `on ReceiveNewIntent with a password generator deeplink data should set the special circumstance to GeneratorShortcut`() {
|
fun `on ReceiveNewIntent with a password generator deeplink data should set the special circumstance to GeneratorShortcut`() {
|
||||||
val viewModel = createViewModel()
|
val viewModel = createViewModel()
|
||||||
val mockIntent = mockk<Intent> {
|
val mockIntent = mockk<Intent> {
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
|
@ -1043,6 +1108,7 @@ private fun createMockFido2RegistrationIntent(
|
||||||
fido2CredentialRequest: Fido2CredentialRequest = createMockFido2CredentialRequest(number = 1),
|
fido2CredentialRequest: Fido2CredentialRequest = createMockFido2CredentialRequest(number = 1),
|
||||||
): Intent = mockk<Intent> {
|
): Intent = mockk<Intent> {
|
||||||
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
every { getFido2CredentialRequestOrNull() } returns fido2CredentialRequest
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
|
@ -1056,6 +1122,7 @@ private fun createMockFido2AssertionIntent(
|
||||||
createMockFido2CredentialAssertionRequest(number = 1),
|
createMockFido2CredentialAssertionRequest(number = 1),
|
||||||
): Intent = mockk<Intent> {
|
): Intent = mockk<Intent> {
|
||||||
every { getFido2AssertionRequestOrNull() } returns fido2CredentialAssertionRequest
|
every { getFido2AssertionRequestOrNull() } returns fido2CredentialAssertionRequest
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
|
@ -1070,6 +1137,7 @@ private fun createMockFido2GetCredentialsIntent(
|
||||||
),
|
),
|
||||||
): Intent = mockk<Intent> {
|
): Intent = mockk<Intent> {
|
||||||
every { getFido2GetCredentialsRequestOrNull() } returns fido2GetCredentialsRequest
|
every { getFido2GetCredentialsRequestOrNull() } returns fido2GetCredentialsRequest
|
||||||
|
every { getTotpDataOrNull() } returns null
|
||||||
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
every { getPasswordlessRequestDataIntentOrNull() } returns null
|
||||||
every { getAutofillSelectionDataOrNull() } returns null
|
every { getAutofillSelectionDataOrNull() } returns null
|
||||||
every { getAutofillSaveItemOrNull() } returns null
|
every { getAutofillSaveItemOrNull() } returns null
|
||||||
|
|
|
@ -7,6 +7,7 @@ import com.x8bit.bitwarden.data.autofill.fido2.model.createMockFido2GetCredentia
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSaveItem
|
||||||
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
import com.x8bit.bitwarden.data.autofill.model.AutofillSelectionData
|
||||||
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
import com.x8bit.bitwarden.data.platform.manager.model.SpecialCircumstance
|
||||||
|
import com.x8bit.bitwarden.ui.vault.model.TotpData
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertNull
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
|
@ -38,6 +39,7 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
data = mockk(),
|
data = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
),
|
),
|
||||||
|
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||||
SpecialCircumstance.PasswordlessRequest(
|
SpecialCircumstance.PasswordlessRequest(
|
||||||
passwordlessRequestData = mockk(),
|
passwordlessRequestData = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
|
@ -87,6 +89,7 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
data = mockk(),
|
data = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
),
|
),
|
||||||
|
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||||
SpecialCircumstance.PasswordlessRequest(
|
SpecialCircumstance.PasswordlessRequest(
|
||||||
passwordlessRequestData = mockk(),
|
passwordlessRequestData = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
|
@ -118,6 +121,7 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
SpecialCircumstance.AutofillSave(
|
SpecialCircumstance.AutofillSave(
|
||||||
autofillSaveItem = mockk(),
|
autofillSaveItem = mockk(),
|
||||||
),
|
),
|
||||||
|
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||||
SpecialCircumstance.ShareNewSend(
|
SpecialCircumstance.ShareNewSend(
|
||||||
data = mockk(),
|
data = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
|
@ -188,6 +192,7 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
data = mockk(),
|
data = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
),
|
),
|
||||||
|
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||||
SpecialCircumstance.PasswordlessRequest(
|
SpecialCircumstance.PasswordlessRequest(
|
||||||
passwordlessRequestData = mockk(),
|
passwordlessRequestData = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
|
@ -234,6 +239,7 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
data = mockk(),
|
data = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
),
|
),
|
||||||
|
mockk<SpecialCircumstance.AddTotpLoginItem>(),
|
||||||
SpecialCircumstance.PasswordlessRequest(
|
SpecialCircumstance.PasswordlessRequest(
|
||||||
passwordlessRequestData = mockk(),
|
passwordlessRequestData = mockk(),
|
||||||
shouldFinishWhenComplete = true,
|
shouldFinishWhenComplete = true,
|
||||||
|
@ -251,4 +257,31 @@ class SpecialCircumstanceExtensionsTest {
|
||||||
assertNull(specialCircumstance.toFido2GetCredentialsRequestOrNull())
|
assertNull(specialCircumstance.toFido2GetCredentialsRequestOrNull())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toTotpDataOrNull should return a non-null value for AddTotpLoginItem`() {
|
||||||
|
val totpData = mockk<TotpData>()
|
||||||
|
assertEquals(
|
||||||
|
totpData,
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = totpData).toTotpDataOrNull(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `toTotpDataOrNull should return a null value for other types`() {
|
||||||
|
listOf(
|
||||||
|
mockk<SpecialCircumstance.AutofillSelection>(),
|
||||||
|
mockk<SpecialCircumstance.AutofillSave>(),
|
||||||
|
mockk<SpecialCircumstance.ShareNewSend>(),
|
||||||
|
mockk<SpecialCircumstance.PasswordlessRequest>(),
|
||||||
|
mockk<SpecialCircumstance.Fido2Save>(),
|
||||||
|
mockk<SpecialCircumstance.Fido2Assertion>(),
|
||||||
|
mockk<SpecialCircumstance.RegistrationEvent>(),
|
||||||
|
SpecialCircumstance.GeneratorShortcut,
|
||||||
|
SpecialCircumstance.VaultShortcut,
|
||||||
|
)
|
||||||
|
.forEach { specialCircumstance ->
|
||||||
|
assertNull(specialCircumstance.toTotpDataOrNull())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ class FakeNavHostController : NavHostController(context = mockk()) {
|
||||||
* Asserts the [currentRoute] matches the given [route].
|
* Asserts the [currentRoute] matches the given [route].
|
||||||
*/
|
*/
|
||||||
fun assertCurrentRoute(route: String) {
|
fun assertCurrentRoute(route: String) {
|
||||||
assertEquals(currentRoute, route)
|
assertEquals(route, currentRoute)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,16 +128,16 @@ class FakeNavHostController : NavHostController(context = mockk()) {
|
||||||
navOptions: NavOptions? = null,
|
navOptions: NavOptions? = null,
|
||||||
navigatorExtras: Navigator.Extras? = null,
|
navigatorExtras: Navigator.Extras? = null,
|
||||||
) {
|
) {
|
||||||
assertEquals(currentRoute, route)
|
assertEquals(route, currentRoute)
|
||||||
assertEquals(lastNavigation?.navOptions, navOptions)
|
assertEquals(navOptions, lastNavigation?.navOptions)
|
||||||
assertEquals(lastNavigation?.navigatorExtras, navigatorExtras)
|
assertEquals(navigatorExtras, lastNavigation?.navigatorExtras)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts the [lastNavigation] includes the given [navOptions].
|
* Asserts the [lastNavigation] includes the given [navOptions].
|
||||||
*/
|
*/
|
||||||
fun assertLastNavOptions(navOptions: NavOptions?) {
|
fun assertLastNavOptions(navOptions: NavOptions?) {
|
||||||
assertEquals(lastNavigation?.navOptions, navOptions)
|
assertEquals(navOptions, lastNavigation?.navOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Navigation(
|
data class Navigation(
|
||||||
|
|
|
@ -151,6 +151,15 @@ class RootNavScreenTest : BaseComposeTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make sure navigating to vault unlocked for new totp works as expected:
|
||||||
|
rootNavStateFlow.value = RootNavState.VaultUnlockedForNewTotp(activeUserId = "userId")
|
||||||
|
composeTestRule.runOnIdle {
|
||||||
|
fakeNavHostController.assertLastNavigation(
|
||||||
|
route = "vault_item_listing_as_root/login",
|
||||||
|
navOptions = expectedNavOptions,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure navigating to vault unlocked for new sends works as expected:
|
// Make sure navigating to vault unlocked for new sends works as expected:
|
||||||
rootNavStateFlow.value = RootNavState.VaultUnlockedForNewSend
|
rootNavStateFlow.value = RootNavState.VaultUnlockedForNewSend
|
||||||
composeTestRule.runOnIdle {
|
composeTestRule.runOnIdle {
|
||||||
|
|
|
@ -414,6 +414,43 @@ class RootNavViewModelTest : BaseViewModelTest() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `when the active user has an unlocked vault but there is an AddTotpLoginItem special circumstance the nav state should be VaultUnlockedForNewTotp`() {
|
||||||
|
specialCircumstanceManager.specialCircumstance =
|
||||||
|
SpecialCircumstance.AddTotpLoginItem(data = mockk())
|
||||||
|
mutableUserStateFlow.tryEmit(
|
||||||
|
UserState(
|
||||||
|
activeUserId = "activeUserId",
|
||||||
|
accounts = listOf(
|
||||||
|
UserState.Account(
|
||||||
|
userId = "activeUserId",
|
||||||
|
name = "name",
|
||||||
|
email = "email",
|
||||||
|
avatarColorHex = "avatarColorHex",
|
||||||
|
environment = Environment.Us,
|
||||||
|
isPremium = true,
|
||||||
|
isLoggedIn = true,
|
||||||
|
isVaultUnlocked = true,
|
||||||
|
needsPasswordReset = false,
|
||||||
|
isBiometricsEnabled = false,
|
||||||
|
organizations = emptyList(),
|
||||||
|
needsMasterPassword = false,
|
||||||
|
trustedDevice = null,
|
||||||
|
hasMasterPassword = true,
|
||||||
|
isUsingKeyConnector = false,
|
||||||
|
onboardingStatus = OnboardingStatus.COMPLETE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val viewModel = createViewModel()
|
||||||
|
assertEquals(
|
||||||
|
RootNavState.VaultUnlockedForNewTotp(activeUserId = "activeUserId"),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("MaxLineLength")
|
@Suppress("MaxLineLength")
|
||||||
@Test
|
@Test
|
||||||
fun `when the active user has an unlocked vault but the is a ShareNewSend special circumstance the nav state should be VaultUnlockedForNewSend`() {
|
fun `when the active user has an unlocked vault but the is a ShareNewSend special circumstance the nav state should be VaultUnlockedForNewSend`() {
|
||||||
|
|
Loading…
Reference in a new issue