mirror of
https://github.com/bitwarden/android.git
synced 2026-06-01 10:16:47 -05:00
BIT-1110: Allow account addition via the account switcher (#305)
This commit is contained in:
@@ -29,6 +29,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.PasswordStrengthResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
||||
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.toSdkParams
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.toUserState
|
||||
@@ -148,6 +149,7 @@ class AuthRepositoryTest {
|
||||
assertEquals(
|
||||
SINGLE_USER_STATE_1.toUserState(
|
||||
vaultState = VAULT_STATE,
|
||||
specialCircumstance = null,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
@@ -156,6 +158,7 @@ class AuthRepositoryTest {
|
||||
assertEquals(
|
||||
MULTI_USER_STATE.toUserState(
|
||||
vaultState = VAULT_STATE,
|
||||
specialCircumstance = null,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
@@ -165,6 +168,7 @@ class AuthRepositoryTest {
|
||||
assertEquals(
|
||||
MULTI_USER_STATE.toUserState(
|
||||
vaultState = emptyVaultState,
|
||||
specialCircumstance = null,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
@@ -185,6 +189,31 @@ class AuthRepositoryTest {
|
||||
assertNull(repository.rememberedEmailAddress)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `specialCircumstance update should trigger a change in UserState`() {
|
||||
// Populate the initial UserState
|
||||
assertNull(repository.specialCircumstance)
|
||||
val initialUserState = SINGLE_USER_STATE_1.toUserState(
|
||||
vaultState = VAULT_STATE,
|
||||
specialCircumstance = null,
|
||||
)
|
||||
mutableVaultStateFlow.value = VAULT_STATE
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
initialUserState,
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
||||
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
|
||||
|
||||
assertEquals(
|
||||
initialUserState.copy(
|
||||
specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `delete account fails if not logged in`() = runTest {
|
||||
val masterPassword = "hello world"
|
||||
@@ -439,6 +468,94 @@ class AuthRepositoryTest {
|
||||
)
|
||||
vaultRepository.sync()
|
||||
}
|
||||
assertEquals(
|
||||
SINGLE_USER_STATE_1,
|
||||
fakeAuthDiskSource.userState,
|
||||
)
|
||||
assertNull(repository.specialCircumstance)
|
||||
verify(exactly = 0) { vaultRepository.lockVaultIfNecessary(any()) }
|
||||
verify { vaultRepository.clearUnlockedData() }
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `login get token succeeds when there is an existing user should switch to the new logged in user and lock the old user's vault`() =
|
||||
runTest {
|
||||
// Ensure the initial state for User 2 with a account addition
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_2
|
||||
repository.specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition
|
||||
|
||||
// Set up login for User 1
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
} returns Result.success(PRE_LOGIN_SUCCESS)
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
}
|
||||
.returns(Result.success(successResponse))
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
userKey = successResponse.key,
|
||||
privateKey = successResponse.privateKey,
|
||||
organizationalKeys = emptyMap(),
|
||||
masterPassword = PASSWORD,
|
||||
)
|
||||
} returns VaultUnlockResult.Success
|
||||
coEvery { vaultRepository.sync() } just runs
|
||||
every {
|
||||
GET_TOKEN_RESPONSE_SUCCESS.toUserState(
|
||||
previousUserState = SINGLE_USER_STATE_2,
|
||||
environmentUrlData = EnvironmentUrlDataJson.DEFAULT_US,
|
||||
)
|
||||
} returns MULTI_USER_STATE
|
||||
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
)
|
||||
fakeAuthDiskSource.assertUserKey(
|
||||
userId = USER_ID_1,
|
||||
userKey = "key",
|
||||
)
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
captchaToken = null,
|
||||
uniqueAppId = UNIQUE_APP_ID,
|
||||
)
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
userKey = successResponse.key,
|
||||
privateKey = successResponse.privateKey,
|
||||
organizationalKeys = emptyMap(),
|
||||
masterPassword = PASSWORD,
|
||||
)
|
||||
vaultRepository.sync()
|
||||
}
|
||||
assertEquals(
|
||||
MULTI_USER_STATE,
|
||||
fakeAuthDiskSource.userState,
|
||||
)
|
||||
assertNull(repository.specialCircumstance)
|
||||
verify { vaultRepository.lockVaultIfNecessary(userId = USER_ID_2) }
|
||||
verify { vaultRepository.clearUnlockedData() }
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -121,6 +121,7 @@ class UserStateJsonExtensionsTest {
|
||||
vaultState = VaultState(
|
||||
unlockedVaultUserIds = setOf("activeUserId"),
|
||||
),
|
||||
specialCircumstance = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -141,6 +142,7 @@ class UserStateJsonExtensionsTest {
|
||||
isVaultUnlocked = false,
|
||||
),
|
||||
),
|
||||
specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition,
|
||||
),
|
||||
UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
@@ -162,6 +164,7 @@ class UserStateJsonExtensionsTest {
|
||||
vaultState = VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
specialCircumstance = UserState.SpecialCircumstance.PendingAccountAddition,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
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.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
@@ -32,6 +33,8 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||
private val environmentRepository = FakeEnvironmentRepository()
|
||||
private val authRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { specialCircumstance } returns null
|
||||
every { specialCircumstance = any() } just runs
|
||||
every { logout() } just runs
|
||||
}
|
||||
private val vaultRepository = mockk<VaultRepository>()
|
||||
@@ -120,12 +123,13 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on AddAccountClick should emit NavigateToLoginScreen`() = runTest {
|
||||
fun `on AddAccountClick should update the SpecialCircumstance of the AuthRepository to PendingAccountAddition`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultUnlockAction.AddAccountClick)
|
||||
assertEquals(VaultUnlockEvent.NavigateToLoginScreen, awaitItem())
|
||||
viewModel.trySendAction(VaultUnlockAction.AddAccountClick)
|
||||
verify {
|
||||
authRepository.specialCircumstance = SpecialCircumstance.PendingAccountAddition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
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.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||
@@ -12,7 +13,10 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
import io.mockk.runs
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -29,6 +33,8 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
private val authRepository: AuthRepository =
|
||||
mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { specialCircumstance } returns null
|
||||
every { specialCircumstance = any() } just runs
|
||||
}
|
||||
|
||||
private val vaultRepository: VaultRepository =
|
||||
@@ -134,12 +140,13 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on AddAccountClick should emit NavigateToLoginScreen`() = runTest {
|
||||
fun `on AddAccountClick should update the SpecialCircumstance of the AuthRepository to PendingAccountAddition`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(VaultAction.AddAccountClick)
|
||||
assertEquals(VaultEvent.NavigateToLoginScreen, awaitItem())
|
||||
viewModel.trySendAction(VaultAction.AddAccountClick)
|
||||
verify {
|
||||
authRepository.specialCircumstance = SpecialCircumstance.PendingAccountAddition
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user