mirror of
https://github.com/bitwarden/android.git
synced 2026-06-01 10:16:47 -05:00
BIT-1082: Implement vault unlock functionality (#263)
This commit is contained in:
@@ -37,6 +37,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentReposito
|
||||
import com.x8bit.bitwarden.data.platform.util.asFailure
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
@@ -47,6 +48,7 @@ import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.runs
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -62,7 +64,10 @@ class AuthRepositoryTest {
|
||||
private val accountsService: AccountsService = mockk()
|
||||
private val identityService: IdentityService = mockk()
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService = mockk()
|
||||
private val vaultRepository: VaultRepository = mockk()
|
||||
private val mutableVaultStateFlow = MutableStateFlow(VAULT_STATE)
|
||||
private val vaultRepository: VaultRepository = mockk() {
|
||||
every { vaultStateFlow } returns mutableVaultStateFlow
|
||||
}
|
||||
private val fakeAuthDiskSource = FakeAuthDiskSource()
|
||||
private val fakeEnvironmentRepository =
|
||||
FakeEnvironmentRepository()
|
||||
@@ -117,6 +122,41 @@ class AuthRepositoryTest {
|
||||
unmockkStatic(GET_TOKEN_RESPONSE_EXTENSIONS_PATH)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `userStateFlow should update with changes to the UserStateJson and VaultState data`() {
|
||||
fakeAuthDiskSource.userState = null
|
||||
assertEquals(
|
||||
null,
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
||||
mutableVaultStateFlow.value = VAULT_STATE
|
||||
fakeAuthDiskSource.userState = SINGLE_USER_STATE_1
|
||||
assertEquals(
|
||||
SINGLE_USER_STATE_1.toUserState(
|
||||
vaultState = VAULT_STATE,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
||||
fakeAuthDiskSource.userState = MULTI_USER_STATE
|
||||
assertEquals(
|
||||
MULTI_USER_STATE.toUserState(
|
||||
vaultState = VAULT_STATE,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
|
||||
val emptyVaultState = VaultState(unlockedVaultUserIds = emptySet())
|
||||
mutableVaultStateFlow.value = emptyVaultState
|
||||
assertEquals(
|
||||
MULTI_USER_STATE.toUserState(
|
||||
vaultState = emptyVaultState,
|
||||
),
|
||||
repository.userStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `rememberedEmailAddress should pull from and update AuthDiskSource`() {
|
||||
// AuthDiskSource and the repository start with the same value.
|
||||
@@ -287,6 +327,7 @@ class AuthRepositoryTest {
|
||||
.returns(Result.success(successResponse))
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
userKey = successResponse.key,
|
||||
@@ -321,6 +362,7 @@ class AuthRepositoryTest {
|
||||
captchaToken = null,
|
||||
)
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
userKey = successResponse.key,
|
||||
@@ -710,6 +752,7 @@ class AuthRepositoryTest {
|
||||
} returns Result.success(successResponse)
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
userKey = successResponse.key,
|
||||
@@ -770,6 +813,7 @@ class AuthRepositoryTest {
|
||||
} returns Result.success(successResponse)
|
||||
coEvery {
|
||||
vaultRepository.unlockVault(
|
||||
userId = USER_ID_1,
|
||||
email = EMAIL,
|
||||
kdf = ACCOUNT_1.profile.toSdkParams(),
|
||||
userKey = successResponse.key,
|
||||
@@ -934,5 +978,8 @@ class AuthRepositoryTest {
|
||||
USER_ID_2 to ACCOUNT_2,
|
||||
),
|
||||
)
|
||||
private val VAULT_STATE = VaultState(
|
||||
unlockedVaultUserIds = setOf(USER_ID_1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.x8bit.bitwarden.data.auth.repository.util
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
@@ -82,4 +84,81 @@ class UserStateJsonExtensionsTest {
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUserState should return the correct UserState for an unlocked vault`() {
|
||||
assertEquals(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
avatarColorHex = "activeAvatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = mapOf(
|
||||
"activeUserId" to AccountJson(
|
||||
profile = mockk() {
|
||||
every { userId } returns "activeUserId"
|
||||
every { name } returns "activeName"
|
||||
every { email } returns "activeEmail"
|
||||
every { avatarColorHex } returns "activeAvatarColorHex"
|
||||
},
|
||||
tokens = mockk(),
|
||||
settings = mockk(),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toUserState(
|
||||
vaultState = VaultState(
|
||||
unlockedVaultUserIds = setOf("activeUserId"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toUserState return the correct UserState for a locked vault`() {
|
||||
assertEquals(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
avatarColorHex = "activeAvatarColorHex",
|
||||
isVaultUnlocked = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
UserStateJson(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = mapOf(
|
||||
"activeUserId" to AccountJson(
|
||||
profile = mockk() {
|
||||
every { userId } returns "activeUserId"
|
||||
every { name } returns "activeName"
|
||||
every { email } returns "activeEmail"
|
||||
every { avatarColorHex } returns "activeAvatarColorHex"
|
||||
},
|
||||
tokens = mockk(),
|
||||
settings = mockk(),
|
||||
),
|
||||
),
|
||||
|
||||
)
|
||||
.toUserState(
|
||||
vaultState = VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSdkSend
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSendView
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.SendData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultState
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult
|
||||
import io.mockk.awaits
|
||||
import io.mockk.coEvery
|
||||
@@ -498,6 +499,12 @@ class VaultRepositoryTest {
|
||||
),
|
||||
)
|
||||
} returns Result.success(InitializeCryptoResult.Success)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSyncForCurrentUser(
|
||||
masterPassword = "mockPassword-1",
|
||||
@@ -507,6 +514,12 @@ class VaultRepositoryTest {
|
||||
VaultUnlockResult.Success,
|
||||
result,
|
||||
)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = setOf("mockId-1"),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
coVerify { syncService.sync() }
|
||||
}
|
||||
|
||||
@@ -638,6 +651,12 @@ class VaultRepositoryTest {
|
||||
),
|
||||
)
|
||||
} returns Result.failure(IllegalStateException())
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSyncForCurrentUser(
|
||||
masterPassword = "mockPassword-1",
|
||||
@@ -647,6 +666,12 @@ class VaultRepositoryTest {
|
||||
VaultUnlockResult.GenericError,
|
||||
result,
|
||||
)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@@ -681,12 +706,24 @@ class VaultRepositoryTest {
|
||||
),
|
||||
)
|
||||
} returns Result.success(InitializeCryptoResult.AuthenticationError)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSyncForCurrentUser(masterPassword = "")
|
||||
assertEquals(
|
||||
VaultUnlockResult.AuthenticationError,
|
||||
result,
|
||||
)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@@ -694,6 +731,12 @@ class VaultRepositoryTest {
|
||||
fun `unlockVaultAndSyncForCurrentUser with missing user state should return InvalidStateError `() =
|
||||
runTest {
|
||||
fakeAuthDiskSource.userState = null
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSyncForCurrentUser(masterPassword = "")
|
||||
|
||||
@@ -701,12 +744,25 @@ class VaultRepositoryTest {
|
||||
VaultUnlockResult.InvalidStateError,
|
||||
result,
|
||||
)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVaultAndSyncForCurrentUser with missing user key should return InvalidStateError `() =
|
||||
runTest {
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVaultAndSyncForCurrentUser(masterPassword = "")
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockId-1",
|
||||
@@ -721,12 +777,24 @@ class VaultRepositoryTest {
|
||||
VaultUnlockResult.InvalidStateError,
|
||||
result,
|
||||
)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `unlockVaultAndSyncForCurrentUser with missing private key should return InvalidStateError `() =
|
||||
runTest {
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
val result = vaultRepository.unlockVaultAndSyncForCurrentUser(masterPassword = "")
|
||||
fakeAuthDiskSource.storeUserKey(
|
||||
userId = "mockId-1",
|
||||
@@ -741,10 +809,17 @@ class VaultRepositoryTest {
|
||||
VaultUnlockResult.InvalidStateError,
|
||||
result,
|
||||
)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unlockVault with initializeCrypto success should return Success`() = runTest {
|
||||
val userId = "userId"
|
||||
val kdf = MOCK_PROFILE.toSdkParams()
|
||||
val email = MOCK_PROFILE.email
|
||||
val masterPassword = "drowssap"
|
||||
@@ -763,7 +838,15 @@ class VaultRepositoryTest {
|
||||
),
|
||||
)
|
||||
} returns InitializeCryptoResult.Success.asSuccess()
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVault(
|
||||
userId = userId,
|
||||
masterPassword = masterPassword,
|
||||
kdf = kdf,
|
||||
email = email,
|
||||
@@ -771,7 +854,14 @@ class VaultRepositoryTest {
|
||||
privateKey = privateKey,
|
||||
organizationalKeys = organizationalKeys,
|
||||
)
|
||||
|
||||
assertEquals(VaultUnlockResult.Success, result)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = setOf(userId),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
@@ -790,6 +880,7 @@ class VaultRepositoryTest {
|
||||
@Test
|
||||
fun `unlockVault with initializeCrypto authentication failure should return AuthenticationError`() =
|
||||
runTest {
|
||||
val userId = "userId"
|
||||
val kdf = MOCK_PROFILE.toSdkParams()
|
||||
val email = MOCK_PROFILE.email
|
||||
val masterPassword = "drowssap"
|
||||
@@ -808,7 +899,15 @@ class VaultRepositoryTest {
|
||||
),
|
||||
)
|
||||
} returns InitializeCryptoResult.AuthenticationError.asSuccess()
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVault(
|
||||
userId = userId,
|
||||
masterPassword = masterPassword,
|
||||
kdf = kdf,
|
||||
email = email,
|
||||
@@ -816,7 +915,14 @@ class VaultRepositoryTest {
|
||||
privateKey = privateKey,
|
||||
organizationalKeys = organizationalKeys,
|
||||
)
|
||||
|
||||
assertEquals(VaultUnlockResult.AuthenticationError, result)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
@@ -833,6 +939,7 @@ class VaultRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `unlockVault with initializeCrypto failure should return GenericError`() = runTest {
|
||||
val userId = "userId"
|
||||
val kdf = MOCK_PROFILE.toSdkParams()
|
||||
val email = MOCK_PROFILE.email
|
||||
val masterPassword = "drowssap"
|
||||
@@ -851,7 +958,15 @@ class VaultRepositoryTest {
|
||||
),
|
||||
)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
|
||||
val result = vaultRepository.unlockVault(
|
||||
userId = userId,
|
||||
masterPassword = masterPassword,
|
||||
kdf = kdf,
|
||||
email = email,
|
||||
@@ -859,7 +974,14 @@ class VaultRepositoryTest {
|
||||
privateKey = privateKey,
|
||||
organizationalKeys = organizationalKeys,
|
||||
)
|
||||
|
||||
assertEquals(VaultUnlockResult.GenericError, result)
|
||||
assertEquals(
|
||||
VaultState(
|
||||
unlockedVaultUserIds = emptySet(),
|
||||
),
|
||||
vaultRepository.vaultStateFlow.value,
|
||||
)
|
||||
coVerify(exactly = 1) {
|
||||
vaultSdkSource.initializeCrypto(
|
||||
request = InitCryptoRequest(
|
||||
@@ -876,6 +998,7 @@ class VaultRepositoryTest {
|
||||
|
||||
@Test
|
||||
fun `unlockVault with initializeCrypto awaiting should block calls to sync`() = runTest {
|
||||
val userId = "userId"
|
||||
val kdf = MOCK_PROFILE.toSdkParams()
|
||||
val email = MOCK_PROFILE.email
|
||||
val masterPassword = "drowssap"
|
||||
@@ -898,6 +1021,7 @@ class VaultRepositoryTest {
|
||||
val scope = CoroutineScope(Dispatchers.Unconfined)
|
||||
scope.launch {
|
||||
vaultRepository.unlockVault(
|
||||
userId = userId,
|
||||
masterPassword = masterPassword,
|
||||
kdf = kdf,
|
||||
email = email,
|
||||
|
||||
@@ -4,6 +4,8 @@ import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
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.platform.repository.EnvironmentRepository
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.Environment
|
||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||
@@ -16,6 +18,7 @@ import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -23,6 +26,9 @@ import org.junit.jupiter.api.Test
|
||||
class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val environmentRepository = FakeEnvironmentRepository()
|
||||
private val authRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns MutableStateFlow(DEFAULT_USER_STATE)
|
||||
}
|
||||
private val vaultRepository = mockk<VaultRepository>()
|
||||
|
||||
@Test
|
||||
@@ -191,17 +197,39 @@ class VaultUnlockViewModelTest : BaseViewModelTest() {
|
||||
vaultRepo: VaultRepository = vaultRepository,
|
||||
): VaultUnlockViewModel = VaultUnlockViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
authRepository = authRepository,
|
||||
vaultRepo = vaultRepo,
|
||||
environmentRepo = environmentRepo,
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_STATE: VaultUnlockState = VaultUnlockState(
|
||||
accountSummaries = emptyList(),
|
||||
avatarColorString = "0000FF",
|
||||
email = "bit@bitwarden.com",
|
||||
initials = "BW",
|
||||
accountSummaries = listOf(
|
||||
AccountSummary(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
),
|
||||
),
|
||||
avatarColorString = "#aa00aa",
|
||||
email = "active@bitwarden.com",
|
||||
initials = "AU",
|
||||
dialog = null,
|
||||
environmentUrl = Environment.Us.label,
|
||||
passwordInput = "",
|
||||
)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -72,6 +72,15 @@ class RootNavScreenTest : BaseComposeTest() {
|
||||
}
|
||||
assertTrue(isSplashScreenRemoved)
|
||||
|
||||
// Make sure navigating to vault locked works as expected:
|
||||
rootNavStateFlow.value = RootNavState.VaultLocked
|
||||
composeTestRule.runOnIdle {
|
||||
fakeNavHostController.assertLastNavigation(
|
||||
route = "vault_unlock",
|
||||
navOptions = expectedNavOptions,
|
||||
)
|
||||
}
|
||||
|
||||
// Make sure navigating to vault unlocked works as expected:
|
||||
rootNavStateFlow.value = RootNavState.VaultUnlocked
|
||||
composeTestRule.runOnIdle {
|
||||
|
||||
@@ -1,64 +1,69 @@
|
||||
package com.x8bit.bitwarden.ui.platform.feature.rootnav
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState.Authenticated
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState.Unauthenticated
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class RootNavViewModelTest : BaseViewModelTest() {
|
||||
|
||||
@Test
|
||||
fun `initial state should be the state in savedStateHandle`() {
|
||||
val authRepository = mockk<AuthRepository> {
|
||||
every { this@mockk.authStateFlow } returns MutableStateFlow(mockk<Authenticated>())
|
||||
}
|
||||
val handle = SavedStateHandle(mapOf(("nav_state" to RootNavState.VaultUnlocked)))
|
||||
val viewModel = RootNavViewModel(
|
||||
authRepository = authRepository,
|
||||
savedStateHandle = handle,
|
||||
)
|
||||
assertEquals(RootNavState.VaultUnlocked, viewModel.stateFlow.value)
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val authRepository = mockk<AuthRepository>() {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when auth state is Uninitialized nav state should be Splash`() {
|
||||
val viewModel = RootNavViewModel(
|
||||
authRepository = mockk {
|
||||
every { this@mockk.authStateFlow } returns MutableStateFlow(AuthState.Uninitialized)
|
||||
},
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
)
|
||||
assertEquals(RootNavState.Splash, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when auth state is Authenticated nav state should be VaultUnlocked`() {
|
||||
val authRepository = mockk<AuthRepository> {
|
||||
every { this@mockk.authStateFlow } returns MutableStateFlow(mockk<Authenticated>())
|
||||
}
|
||||
val viewModel = RootNavViewModel(
|
||||
authRepository = authRepository,
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
)
|
||||
assertEquals(RootNavState.VaultUnlocked, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when auth state is Unauthenticated nav state should be Auth`() = runTest {
|
||||
val viewModel = RootNavViewModel(
|
||||
authRepository = mockk {
|
||||
every { this@mockk.authStateFlow } returns MutableStateFlow(Unauthenticated)
|
||||
},
|
||||
savedStateHandle = SavedStateHandle(),
|
||||
)
|
||||
fun `when there are no accounts the nav state should be Auth`() {
|
||||
mutableUserStateFlow.tryEmit(null)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.Auth, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the active user has an unlocked vault the nav state should be VaultUnlocked`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.VaultUnlocked, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the active user has a locked vault the nav state should be VaultLocked`() {
|
||||
mutableUserStateFlow.tryEmit(
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isVaultUnlocked = false,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(RootNavState.VaultLocked, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
private fun createViewModel(): RootNavViewModel =
|
||||
RootNavViewModel(
|
||||
authRepository = authRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.vault
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
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.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView
|
||||
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView
|
||||
@@ -19,9 +20,17 @@ import org.junit.jupiter.api.Test
|
||||
|
||||
class VaultViewModelTest : BaseViewModelTest() {
|
||||
|
||||
private val mutableUserStateFlow =
|
||||
MutableStateFlow<UserState?>(DEFAULT_USER_STATE)
|
||||
|
||||
private val mutableVaultDataStateFlow =
|
||||
MutableStateFlow<DataState<VaultData>>(DataState.Loading)
|
||||
|
||||
private val authRepository: AuthRepository =
|
||||
mockk {
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
}
|
||||
|
||||
private val vaultRepository: VaultRepository =
|
||||
mockk {
|
||||
every { vaultDataStateFlow } returns mutableVaultDataStateFlow
|
||||
@@ -29,19 +38,54 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when not set`() {
|
||||
fun `initial state should be correct`() {
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `initial state should be correct when set`() {
|
||||
val state = DEFAULT_STATE.copy(
|
||||
initials = "WB",
|
||||
avatarColorString = "00FF00",
|
||||
fun `UserState updates with a null value should do nothing`() {
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||
|
||||
mutableUserStateFlow.value = null
|
||||
|
||||
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UserState updates with a non-null value update the account information in the state`() {
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(DEFAULT_STATE, viewModel.stateFlow.value)
|
||||
|
||||
mutableUserStateFlow.value = DEFAULT_USER_STATE.copy(
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "Other User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#00aaaa",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
avatarColorString = "#00aaaa",
|
||||
initials = "OU",
|
||||
accountSummaries = listOf(
|
||||
AccountSummary(
|
||||
userId = "activeUserId",
|
||||
name = "Other User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#00aaaa",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
val viewModel = createViewModel(state = state)
|
||||
assertEquals(state, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -294,23 +338,41 @@ class VaultViewModelTest : BaseViewModelTest() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createViewModel(
|
||||
state: VaultState? = DEFAULT_STATE,
|
||||
): VaultViewModel = VaultViewModel(
|
||||
savedStateHandle = SavedStateHandle().apply { set("state", state) },
|
||||
vaultRepository = vaultRepository,
|
||||
)
|
||||
private fun createViewModel(): VaultViewModel =
|
||||
VaultViewModel(
|
||||
authRepository = authRepository,
|
||||
vaultRepository = vaultRepository,
|
||||
)
|
||||
}
|
||||
|
||||
private const val DEFAULT_COLOR_STRING: String = "FF0000FF"
|
||||
private const val DEFAULE_INITIALS: String = "BW"
|
||||
private val DEFAULT_STATE: VaultState =
|
||||
createMockVaultState(viewState = VaultState.ViewState.Loading)
|
||||
|
||||
private val DEFAULT_USER_STATE = UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private fun createMockVaultState(viewState: VaultState.ViewState): VaultState =
|
||||
VaultState(
|
||||
avatarColorString = DEFAULT_COLOR_STRING,
|
||||
initials = DEFAULE_INITIALS,
|
||||
accountSummaries = emptyList(),
|
||||
avatarColorString = "#aa00aa",
|
||||
initials = "AU",
|
||||
accountSummaries = listOf(
|
||||
AccountSummary(
|
||||
userId = "activeUserId",
|
||||
name = "Active User",
|
||||
email = "active@bitwarden.com",
|
||||
avatarColorHex = "#aa00aa",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
),
|
||||
),
|
||||
viewState = viewState,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class UserStateExtensionsTest {
|
||||
@Test
|
||||
fun `toAccountSummaries should return the correct list`() {
|
||||
assertEquals(
|
||||
listOf(
|
||||
AccountSummary(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
avatarColorHex = "activeAvatarColorHex",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
),
|
||||
AccountSummary(
|
||||
userId = "lockedUserId",
|
||||
name = "lockedName",
|
||||
email = "lockedEmail",
|
||||
avatarColorHex = "lockedAvatarColorHex",
|
||||
status = AccountSummary.Status.LOCKED,
|
||||
),
|
||||
AccountSummary(
|
||||
userId = "unlockedUserId",
|
||||
name = "unlockedName",
|
||||
email = "unlockedEmail",
|
||||
avatarColorHex = "unlockedAvatarColorHex",
|
||||
status = AccountSummary.Status.UNLOCKED,
|
||||
),
|
||||
),
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "activeName",
|
||||
email = "activeEmail",
|
||||
avatarColorHex = "activeAvatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "lockedUserId",
|
||||
name = "lockedName",
|
||||
email = "lockedEmail",
|
||||
avatarColorHex = "lockedAvatarColorHex",
|
||||
isVaultUnlocked = false,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "unlockedUserId",
|
||||
name = "unlockedName",
|
||||
email = "unlockedEmail",
|
||||
avatarColorHex = "unlockedAvatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toAccountSummaries(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toAccountSummary for an active account should return an active AccountSummary`() {
|
||||
assertEquals(
|
||||
AccountSummary(
|
||||
userId = "userId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "userId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
)
|
||||
.toAccountSummary(isActive = true),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toAccountSummary for an locked account should return a locked AccountSummary`() {
|
||||
assertEquals(
|
||||
AccountSummary(
|
||||
userId = "userId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
status = AccountSummary.Status.LOCKED,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "userId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isVaultUnlocked = false,
|
||||
)
|
||||
.toAccountSummary(isActive = false),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toAccountSummary for a unlocked account should return a locked AccountSummary`() {
|
||||
assertEquals(
|
||||
AccountSummary(
|
||||
userId = "userId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
status = AccountSummary.Status.UNLOCKED,
|
||||
),
|
||||
UserState.Account(
|
||||
userId = "userId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
)
|
||||
.toAccountSummary(isActive = false),
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toActiveAccountSummary should return an active AccountSummary`() {
|
||||
assertEquals(
|
||||
AccountSummary(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
status = AccountSummary.Status.ACTIVE,
|
||||
),
|
||||
UserState(
|
||||
activeUserId = "activeUserId",
|
||||
accounts = listOf(
|
||||
UserState.Account(
|
||||
userId = "activeUserId",
|
||||
name = "name",
|
||||
email = "email",
|
||||
avatarColorHex = "avatarColorHex",
|
||||
isVaultUnlocked = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toActiveAccountSummary(),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user