BIT-1498: Allow external navigation to Add Send screen (#685)

This commit is contained in:
Brian Yencho authored and Álison Fernandes committed 2024-06-20 17:08:07 +01:00
1 parent bdca79d862
commit eeb22dbfee
24 files changed
+358 -58

No files matched your search

@@ -4,17 +4,23 @@ import android.content.Intent
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.auth.repository.util.getCaptchaCallbackTokenResult
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
import com.x8bit.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
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 MainViewModelTest : BaseViewModelTest() {
@@ -24,18 +30,27 @@ class MainViewModelTest : BaseViewModelTest() {
val authRepository = mockk<AuthRepository> {
every { userStateFlow } returns mutableUserStateFlow
every { activeUserId } returns USER_ID
every {
setCaptchaCallbackTokenResult(
tokenResult = CaptchaCallbackTokenResult.Success(
token = "mockk_token",
),
)
} just runs
every { specialCircumstance } returns null
every { specialCircumstance = any() } just runs
every { setCaptchaCallbackTokenResult(any()) } just runs
}
private val settingsRepository = mockk<SettingsRepository> {
every { appTheme } returns AppTheme.DEFAULT
every { appThemeStateFlow } returns mutableAppThemeFlow
}
private val intentManager: IntentManager = mockk {
every { getShareDataFromIntent(any()) } returns null
}
@BeforeEach
fun setUp() {
mockkStatic(CAPTCHA_UTILS_PATH)
}
@AfterEach
fun tearDown() {
unmockkStatic(CAPTCHA_UTILS_PATH)
}
@Test
fun `on AppThemeChanged should update state`() {
@@ -65,14 +80,37 @@ class MainViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `on ReceiveFirstIntent with share data should set the special circumstance to ShareNewSend`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent>()
val shareData = mockk<IntentManager.ShareData>()
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
viewModel.trySendAction(
MainAction.ReceiveFirstIntent(
intent = mockIntent,
),
)
verify {
authRepository.specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = shareData,
shouldFinishWhenComplete = true,
)
}
}
@Test
fun `on ReceiveNewIntent with captcha host should call setCaptchaCallbackToken`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent> {
every { data?.host } returns "captcha-callback"
every { data?.getQueryParameter("token") } returns "mockk_token"
every { action } returns Intent.ACTION_VIEW
}
val mockIntent = mockk<Intent>()
every {
mockIntent.getCaptchaCallbackTokenResult()
} returns CaptchaCallbackTokenResult.Success(
token = "mockk_token",
)
viewModel.trySendAction(
MainAction.ReceiveNewIntent(
intent = mockIntent,
@@ -87,12 +125,37 @@ class MainViewModelTest : BaseViewModelTest() {
}
}
@Suppress("MaxLineLength")
@Test
fun `on ReceiveNewIntent with share data should set the special circumstance to ShareNewSend`() {
val viewModel = createViewModel()
val mockIntent = mockk<Intent>()
val shareData = mockk<IntentManager.ShareData>()
every { mockIntent.getCaptchaCallbackTokenResult() } returns null
every { intentManager.getShareDataFromIntent(mockIntent) } returns shareData
viewModel.trySendAction(
MainAction.ReceiveNewIntent(
intent = mockIntent,
),
)
verify {
authRepository.specialCircumstance = UserState.SpecialCircumstance.ShareNewSend(
data = shareData,
shouldFinishWhenComplete = false,
)
}
}
private fun createViewModel() = MainViewModel(
authRepository = authRepository,
settingsRepository = settingsRepository,
intentManager = intentManager,
)
companion object {
private const val CAPTCHA_UTILS_PATH =
"com.x8bit.bitwarden.data.auth.repository.util.CaptchaUtilsKt"
private const val USER_ID = "userID"
private val DEFAULT_USER_STATE = UserState(
activeUserId = USER_ID,
@@ -68,6 +68,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
@@ -217,6 +218,7 @@ class AuthRepositoryTest {
SINGLE_USER_STATE_1.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
),
@@ -238,6 +240,7 @@ class AuthRepositoryTest {
MULTI_USER_STATE.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
),
@@ -253,6 +256,7 @@ class AuthRepositoryTest {
MULTI_USER_STATE.toUserState(
vaultState = emptyVaultState,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
),
@@ -277,6 +281,7 @@ class AuthRepositoryTest {
MULTI_USER_STATE.toUserState(
vaultState = emptyVaultState,
userOrganizationsList = USER_ORGANIZATIONS,
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
),
@@ -306,6 +311,7 @@ class AuthRepositoryTest {
val initialUserState = SINGLE_USER_STATE_1.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
@@ -316,11 +322,12 @@ class AuthRepositoryTest {
repository.userStateFlow.value,
)
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
val mockSpecialCircumstance: UserState.SpecialCircumstance = mockk()
repository.specialCircumstance = mockSpecialCircumstance
assertEquals(
initialUserState.copy(
specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition,
specialCircumstance = mockSpecialCircumstance,
),
repository.userStateFlow.value,
)
@@ -595,7 +602,7 @@ class AuthRepositoryTest {
runTest {
// Ensure the initial state for User 2 with a account addition
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
repository.hasPendingAccountAddition = true
// Set up login for User 1
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
@@ -665,7 +672,7 @@ class AuthRepositoryTest {
MULTI_USER_STATE,
fakeAuthDiskSource.userState,
)
assertNull(repository.specialCircumstance)
assertFalse(repository.hasPendingAccountAddition)
verify { settingsRepository.setDefaultsIfNecessary(userId = USER_ID_1) }
verify { vaultRepository.clearUnlockedData() }
}
@@ -1076,11 +1083,12 @@ class AuthRepositoryTest {
@Suppress("MaxLineLength")
@Test
fun `switchAccount when the given userId is the same as the current activeUserId should only clear any special circumstances`() {
fun `switchAccount when the given userId is the same as the current activeUserId should reset any pending account additions`() {
val originalUserId = USER_ID_1
val originalUserState = SINGLE_USER_STATE_1.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
@@ -1089,7 +1097,7 @@ class AuthRepositoryTest {
originalUserState,
repository.userStateFlow.value,
)
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
repository.hasPendingAccountAddition = true
assertEquals(
SwitchAccountResult.NoChange,
@@ -1100,7 +1108,7 @@ class AuthRepositoryTest {
originalUserState,
repository.userStateFlow.value,
)
assertNull(repository.specialCircumstance)
assertFalse(repository.hasPendingAccountAddition)
verify(exactly = 0) { vaultRepository.clearUnlockedData() }
}
@@ -1111,6 +1119,7 @@ class AuthRepositoryTest {
val originalUserState = SINGLE_USER_STATE_1.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
@@ -1134,11 +1143,12 @@ class AuthRepositoryTest {
@Suppress("MaxLineLength")
@Test
fun `switchAccount when the userId is valid should update the current UserState, clear the previously unlocked data, and reset the special circumstance`() {
fun `switchAccount when the userId is valid should update the current UserState, clear the previously unlocked data, and reset any pending account additions`() {
val updatedUserId = USER_ID_2
val originalUserState = MULTI_USER_STATE.toUserState(
vaultState = VAULT_STATE,
userOrganizationsList = emptyList(),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
)
@@ -1147,7 +1157,7 @@ class AuthRepositoryTest {
originalUserState,
repository.userStateFlow.value,
)
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
repository.hasPendingAccountAddition = true
assertEquals(
SwitchAccountResult.AccountSwitched,
@@ -1158,7 +1168,7 @@ class AuthRepositoryTest {
originalUserState.copy(activeUserId = updatedUserId),
repository.userStateFlow.value,
)
assertNull(repository.specialCircumstance)
assertFalse(repository.hasPendingAccountAddition)
verify { vaultRepository.clearUnlockedData() }
}
@@ -154,6 +154,7 @@ class UserStateJsonExtensionsTest {
),
),
),
hasPendingAccountAddition = false,
specialCircumstance = null,
vaultUnlockTypeProvider = { VaultUnlockType.PIN },
),
@@ -185,7 +186,8 @@ class UserStateJsonExtensionsTest {
vaultUnlockType = VaultUnlockType.MASTER_PASSWORD,
),
),
specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition,
hasPendingAccountAddition = true,
specialCircumstance = MOCK_SPECIAL_CIRCUMSTANCE,
),
UserStateJson(
activeUserId = "activeUserId",
@@ -224,9 +226,12 @@ class UserStateJsonExtensionsTest {
),
),
),
specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition,
hasPendingAccountAddition = true,
specialCircumstance = MOCK_SPECIAL_CIRCUMSTANCE,
vaultUnlockTypeProvider = { VaultUnlockType.MASTER_PASSWORD },
),
)
}
}
private val MOCK_SPECIAL_CIRCUMSTANCE: UserState.SpecialCircumstance = mockk()
@@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJso
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.UserState.SpecialCircumstance
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
import com.x8bit.bitwarden.data.platform.repository.model.Environment
@@ -38,8 +37,8 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
private val authRepository = mockk<AuthRepository>() {
every { activeUserId } answers { mutableUserStateFlow.value?.activeUserId }
every { userStateFlow } returns mutableUserStateFlow
every { specialCircumstance } returns null
every { specialCircumstance = any() } just runs
every { hasPendingAccountAddition } returns false
every { hasPendingAccountAddition = any() } just runs
every { logout() } just runs
every { logout(any()) } just runs
every { switchAccount(any()) } returns SwitchAccountResult.AccountSwitched
@@ -174,11 +173,11 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
@Suppress("MaxLineLength")
@Test
fun `on AddAccountClick should update the SpecialCircumstance of the AuthRepository to PendingAccountAddition`() {
fun `on AddAccountClick should set hasPendingAccountAddition to true on the AuthRepository`() {
val viewModel = createViewModel()
viewModel.trySendAction(VaultUnlockAction.AddAccountClick)
verify {
authRepository.specialCircumstance = SpecialCircumstance.PendingAccountAddition
authRepository.hasPendingAccountAddition = true
}
}
@@ -89,5 +89,14 @@ class RootNavScreenTest : BaseComposeTest() {
navOptions = expectedNavOptions,
)
}
// Make sure navigating to vault unlocked works as expected:
rootNavStateFlow.value = RootNavState.VaultUnlockedForNewSend
composeTestRule.runOnIdle {
fakeNavHostController.assertLastNavigation(
route = "vault_unlocked_for_new_send_graph",
navOptions = expectedNavOptions,
)
}
}
}
@@ -6,7 +6,6 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.Organization
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.auth.repository.model.UserState.SpecialCircumstance
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.model.Environment
@@ -53,8 +52,8 @@ class VaultViewModelTest : BaseViewModelTest() {
private val authRepository: AuthRepository =
mockk {
every { userStateFlow } returns mutableUserStateFlow
every { specialCircumstance } returns null
every { specialCircumstance = any() } just runs
every { hasPendingAccountAddition } returns false
every { hasPendingAccountAddition = any() } just runs
every { logout(any()) } just runs
every { switchAccount(any()) } answers { switchAccountResult }
}
@@ -289,11 +288,11 @@ class VaultViewModelTest : BaseViewModelTest() {
@Suppress("MaxLineLength")
@Test
fun `on AddAccountClick should update the SpecialCircumstance of the AuthRepository to PendingAccountAddition`() {
fun `on AddAccountClick should set hasPendingAccountAddition to true on the AuthRepository`() {
val viewModel = createViewModel()
viewModel.trySendAction(VaultAction.AddAccountClick)
verify {
authRepository.specialCircumstance = SpecialCircumstance.PendingAccountAddition
authRepository.hasPendingAccountAddition = true
}
}