mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 10:48:47 +03:00
BIT-808: Conditionally show log in with device on login (#681)
This commit is contained in:
parent
2fa7851b42
commit
6cbfff254c
14 changed files with 265 additions and 14 deletions
|
@ -0,0 +1,16 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
|
||||
/**
|
||||
* Defines raw calls under the /devices API.
|
||||
*/
|
||||
interface DevicesApi {
|
||||
|
||||
@GET("/devices/knowndevice")
|
||||
suspend fun getIsKnownDevice(
|
||||
@Header(value = "X-Request-Email") emailAddress: String,
|
||||
@Header(value = "X-Device-Identifier") deviceId: String,
|
||||
): Result<Boolean>
|
||||
}
|
|
@ -2,6 +2,8 @@ package com.x8bit.bitwarden.data.auth.datasource.network.di
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedServiceImpl
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
|
@ -33,6 +35,14 @@ object AuthNetworkModule {
|
|||
json = json,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesDevicesService(
|
||||
retrofits: Retrofits,
|
||||
): DevicesService = DevicesServiceImpl(
|
||||
devicesApi = retrofits.unauthenticatedApiRetrofit.create(),
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesIdentityService(
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
/**
|
||||
* Provides an API for interacting with the /devices endpoints.
|
||||
*/
|
||||
interface DevicesService {
|
||||
/**
|
||||
* Check whether this device is known (and thus whether Login with Device is available).
|
||||
*/
|
||||
suspend fun getIsKnownDevice(
|
||||
emailAddress: String,
|
||||
deviceId: String,
|
||||
): Result<Boolean>
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.DevicesApi
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
||||
|
||||
class DevicesServiceImpl(
|
||||
private val devicesApi: DevicesApi,
|
||||
) : DevicesService {
|
||||
override suspend fun getIsKnownDevice(
|
||||
emailAddress: String,
|
||||
deviceId: String,
|
||||
): Result<Boolean> = devicesApi.getIsKnownDevice(
|
||||
emailAddress = emailAddress.base64UrlEncode(),
|
||||
deviceId = deviceId,
|
||||
)
|
||||
}
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.repository
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
|
||||
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
|
||||
|
@ -93,6 +94,11 @@ interface AuthRepository : AuthenticatorProvider {
|
|||
*/
|
||||
fun setCaptchaCallbackTokenResult(tokenResult: CaptchaCallbackTokenResult)
|
||||
|
||||
/**
|
||||
* Get a [Boolean] indicating whether this is a known device.
|
||||
*/
|
||||
suspend fun getIsKnownDevice(emailAddress: String): KnownDeviceResult
|
||||
|
||||
/**
|
||||
* Attempts to get the number of times the given [password] has been breached.
|
||||
*/
|
||||
|
|
|
@ -11,6 +11,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenRespon
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
|
@ -19,6 +20,7 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
|
||||
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
|
||||
|
@ -42,7 +44,6 @@ import com.x8bit.bitwarden.data.platform.util.flatMap
|
|||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
|
@ -58,8 +59,9 @@ import javax.inject.Singleton
|
|||
*/
|
||||
@Suppress("LongParameterList", "TooManyFunctions")
|
||||
@Singleton
|
||||
class AuthRepositoryImpl constructor(
|
||||
class AuthRepositoryImpl(
|
||||
private val accountsService: AccountsService,
|
||||
private val devicesService: DevicesService,
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService,
|
||||
private val identityService: IdentityService,
|
||||
private val authSdkSource: AuthSdkSource,
|
||||
|
@ -101,7 +103,6 @@ class AuthRepositoryImpl constructor(
|
|||
initialValue = AuthState.Uninitialized,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override val userStateFlow: StateFlow<UserState?> = combine(
|
||||
authDiskSource.userStateFlow,
|
||||
authDiskSource.userOrganizationsListFlow,
|
||||
|
@ -377,6 +378,17 @@ class AuthRepositoryImpl constructor(
|
|||
mutableCaptchaTokenFlow.tryEmit(tokenResult)
|
||||
}
|
||||
|
||||
override suspend fun getIsKnownDevice(emailAddress: String): KnownDeviceResult =
|
||||
devicesService
|
||||
.getIsKnownDevice(
|
||||
emailAddress = emailAddress,
|
||||
deviceId = authDiskSource.uniqueAppId,
|
||||
)
|
||||
.fold(
|
||||
onFailure = { KnownDeviceResult.Error },
|
||||
onSuccess = { KnownDeviceResult.Success(it) },
|
||||
)
|
||||
|
||||
override suspend fun getPasswordBreachCount(password: String): BreachCountResult =
|
||||
haveIBeenPwnedService
|
||||
.getPasswordBreachCount(password)
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.repository.di
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
|
@ -27,8 +28,10 @@ object AuthRepositoryModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
@Suppress("LongParameterList")
|
||||
fun providesAuthRepository(
|
||||
accountsService: AccountsService,
|
||||
devicesService: DevicesService,
|
||||
identityService: IdentityService,
|
||||
haveIBeenPwnedService: HaveIBeenPwnedService,
|
||||
authSdkSource: AuthSdkSource,
|
||||
|
@ -40,6 +43,7 @@ object AuthRepositoryModule {
|
|||
userLogoutManager: UserLogoutManager,
|
||||
): AuthRepository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
devicesService = devicesService,
|
||||
identityService = identityService,
|
||||
authSdkSource = authSdkSource,
|
||||
authDiskSource = authDiskSource,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
/**
|
||||
* Models result of checking whether this is a known device.
|
||||
*/
|
||||
sealed class KnownDeviceResult {
|
||||
/**
|
||||
* Contains a [Boolean] indicating whether this is a known device.
|
||||
*/
|
||||
data class Success(val isKnownDevice: Boolean) : KnownDeviceResult()
|
||||
|
||||
/**
|
||||
* There was an error determining if this is a known device.
|
||||
*/
|
||||
data object Error : KnownDeviceResult()
|
||||
}
|
|
@ -244,17 +244,18 @@ private fun LoginScreenContent(
|
|||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// TODO BIT-808: Hide button for first-time users
|
||||
BitwardenOutlinedButtonWithIcon(
|
||||
label = stringResource(id = R.string.log_in_with_device),
|
||||
icon = painterResource(id = R.drawable.ic_device),
|
||||
onClick = onLoginWithDeviceClick,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "LogInWithAnotherDeviceButton" }
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
if (state.shouldShowLoginWithDevice) {
|
||||
BitwardenOutlinedButtonWithIcon(
|
||||
label = stringResource(id = R.string.log_in_with_device),
|
||||
icon = painterResource(id = R.drawable.ic_device),
|
||||
onClick = onLoginWithDeviceClick,
|
||||
modifier = Modifier
|
||||
.semantics { testTag = "LogInWithAnotherDeviceButton" }
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
BitwardenOutlinedButtonWithIcon(
|
||||
label = stringResource(id = R.string.log_in_sso),
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||
|
@ -45,10 +46,11 @@ class LoginViewModel @Inject constructor(
|
|||
isLoginButtonEnabled = false,
|
||||
passwordInput = "",
|
||||
environmentLabel = environmentRepository.environment.label,
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
loadingDialogState = LoadingDialogState.Shown(R.string.loading.asText()),
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
captchaToken = LoginArgs(savedStateHandle).captchaToken,
|
||||
accountSummaries = authRepository.userStateFlow.value?.toAccountSummaries().orEmpty(),
|
||||
shouldShowLoginWithDevice = false,
|
||||
),
|
||||
) {
|
||||
|
||||
|
@ -66,6 +68,14 @@ class LoginViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
.launchIn(viewModelScope)
|
||||
|
||||
viewModelScope.launch {
|
||||
trySendAction(
|
||||
LoginAction.Internal.ReceiveKnownDeviceResult(
|
||||
knownDeviceResult = authRepository.getIsKnownDevice(state.emailAddress),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleAction(action: LoginAction) {
|
||||
|
@ -89,6 +99,10 @@ class LoginViewModel @Inject constructor(
|
|||
is LoginAction.Internal.ReceiveLoginResult -> {
|
||||
handleReceiveLoginResult(action = action)
|
||||
}
|
||||
|
||||
is LoginAction.Internal.ReceiveKnownDeviceResult -> {
|
||||
handleKnownDeviceResultReceived(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,6 +123,30 @@ class LoginViewModel @Inject constructor(
|
|||
authRepository.switchAccount(userId = action.accountSummary.userId)
|
||||
}
|
||||
|
||||
private fun handleKnownDeviceResultReceived(
|
||||
action: LoginAction.Internal.ReceiveKnownDeviceResult,
|
||||
) {
|
||||
when (action.knownDeviceResult) {
|
||||
is KnownDeviceResult.Success -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
shouldShowLoginWithDevice = action.knownDeviceResult.isKnownDevice,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is KnownDeviceResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
shouldShowLoginWithDevice = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleReceiveLoginResult(action: LoginAction.Internal.ReceiveLoginResult) {
|
||||
when (val loginResult = action.loginResult) {
|
||||
is LoginResult.CaptchaRequired -> {
|
||||
|
@ -235,6 +273,7 @@ data class LoginState(
|
|||
val loadingDialogState: LoadingDialogState,
|
||||
val errorDialogState: BasicDialogState,
|
||||
val accountSummaries: List<AccountSummary>,
|
||||
val shouldShowLoginWithDevice: Boolean,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
|
@ -352,6 +391,13 @@ sealed class LoginAction {
|
|||
val tokenResult: CaptchaCallbackTokenResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates that a [KnownDeviceResult] has been received and state should be updated.
|
||||
*/
|
||||
data class ReceiveKnownDeviceResult(
|
||||
val knownDeviceResult: KnownDeviceResult,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* Indicates a login result has been received.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.DevicesApi
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import retrofit2.create
|
||||
|
||||
class DevicesServiceTest : BaseServiceTest() {
|
||||
|
||||
private val devicesApi: DevicesApi = retrofit.create()
|
||||
private val service = DevicesServiceImpl(
|
||||
devicesApi = devicesApi,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `getIsKnownDevice when request response is Failure should return Failure`() = runTest {
|
||||
val response = MockResponse().setResponseCode(400)
|
||||
server.enqueue(response)
|
||||
val actual = service.getIsKnownDevice("email", "id")
|
||||
assertTrue(actual.isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getIsKnownDevice when request response is Success should return Success`() = runTest {
|
||||
val response = MockResponse().setBody("false").setResponseCode(200)
|
||||
server.enqueue(response)
|
||||
val actual = service.getIsKnownDevice("email", "id")
|
||||
assertTrue(actual.isSuccess)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenRespon
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.DevicesService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.HaveIBeenPwnedService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.IdentityService
|
||||
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
|
||||
|
@ -28,6 +29,7 @@ import com.x8bit.bitwarden.data.auth.manager.UserLogoutManager
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.AuthState
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.BreachCountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.DeleteAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.KnownDeviceResult
|
||||
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
|
||||
|
@ -76,6 +78,7 @@ class AuthRepositoryTest {
|
|||
|
||||
private val dispatcherManager: DispatcherManager = FakeDispatcherManager()
|
||||
private val accountsService: AccountsService = mockk()
|
||||
private val devicesService: DevicesService = mockk()
|
||||
private val identityService: IdentityService = mockk()
|
||||
private val haveIBeenPwnedService: HaveIBeenPwnedService = mockk()
|
||||
private val mutableVaultStateFlow = MutableStateFlow(VAULT_STATE)
|
||||
|
@ -127,6 +130,7 @@ class AuthRepositoryTest {
|
|||
|
||||
private val repository = AuthRepositoryImpl(
|
||||
accountsService = accountsService,
|
||||
devicesService = devicesService,
|
||||
identityService = identityService,
|
||||
haveIBeenPwnedService = haveIBeenPwnedService,
|
||||
authSdkSource = authSdkSource,
|
||||
|
@ -1173,6 +1177,35 @@ class AuthRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getIsKnownDevice should return failure when service returns failure`() = runTest {
|
||||
coEvery {
|
||||
devicesService.getIsKnownDevice(EMAIL, UNIQUE_APP_ID)
|
||||
} returns Throwable("Fail").asFailure()
|
||||
|
||||
val result = repository.getIsKnownDevice(EMAIL)
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
devicesService.getIsKnownDevice(EMAIL, UNIQUE_APP_ID)
|
||||
}
|
||||
assertEquals(KnownDeviceResult.Error, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getIsKnownDevice should return success when service returns success`() = runTest {
|
||||
val isKnownDevice = true
|
||||
coEvery {
|
||||
devicesService.getIsKnownDevice(EMAIL, UNIQUE_APP_ID)
|
||||
} returns isKnownDevice.asSuccess()
|
||||
|
||||
val result = repository.getIsKnownDevice(EMAIL)
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
devicesService.getIsKnownDevice(EMAIL, UNIQUE_APP_ID)
|
||||
}
|
||||
assertEquals(KnownDeviceResult.Success(isKnownDevice), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getPasswordBreachCount should return failure when service returns failure`() = runTest {
|
||||
val password = "password"
|
||||
|
|
|
@ -203,6 +203,17 @@ class LoginScreenTest : BaseComposeTest() {
|
|||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `log in with device button visibility should update according to state`() {
|
||||
val buttonText = "Log in with device"
|
||||
composeTestRule.onNodeWithText(buttonText).assertDoesNotExist()
|
||||
|
||||
mutableStateFlow.update {
|
||||
it.copy(shouldShowLoginWithDevice = true)
|
||||
}
|
||||
composeTestRule.onNodeWithText(buttonText).assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `close button click should send CloseButtonClick action`() {
|
||||
composeTestRule.onNodeWithContentDescription("Close").performClick()
|
||||
|
@ -302,4 +313,5 @@ private val DEFAULT_STATE =
|
|||
loadingDialogState = LoadingDialogState.Hidden,
|
||||
errorDialogState = BasicDialogState.Hidden,
|
||||
accountSummaries = emptyList(),
|
||||
shouldShowLoginWithDevice = false,
|
||||
)
|
||||
|
|
|
@ -6,6 +6,7 @@ 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.KnownDeviceResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||
|
@ -45,6 +46,9 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
bufferedMutableSharedFlow<CaptchaCallbackTokenResult>()
|
||||
private val mutableUserStateFlow = MutableStateFlow<UserState?>(null)
|
||||
private val authRepository: AuthRepository = mockk(relaxed = true) {
|
||||
coEvery {
|
||||
getIsKnownDevice("test@gmail.com")
|
||||
} returns KnownDeviceResult.Success(false)
|
||||
every { captchaTokenResultFlow } returns mutableCaptchaTokenResultFlow
|
||||
every { userStateFlow } returns mutableUserStateFlow
|
||||
every { logout(any()) } just runs
|
||||
|
@ -150,6 +154,33 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should set shouldShowLoginWithDevice when isKnownDevice returns true`() = runTest {
|
||||
val expectedState = DEFAULT_STATE.copy(
|
||||
shouldShowLoginWithDevice = true,
|
||||
)
|
||||
coEvery {
|
||||
authRepository.getIsKnownDevice("test@gmail.com")
|
||||
} returns KnownDeviceResult.Success(true)
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(expectedState, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should have default state when isKnownDevice returns error`() = runTest {
|
||||
coEvery {
|
||||
authRepository.getIsKnownDevice("test@gmail.com")
|
||||
} returns KnownDeviceResult.Error
|
||||
val viewModel = createViewModel()
|
||||
|
||||
viewModel.stateFlow.test {
|
||||
assertEquals(DEFAULT_STATE, awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `on AddAccountClick should send NavigateBack`() = runTest {
|
||||
|
@ -434,6 +465,7 @@ class LoginViewModelTest : BaseViewModelTest() {
|
|||
errorDialogState = BasicDialogState.Hidden,
|
||||
captchaToken = null,
|
||||
accountSummaries = emptyList(),
|
||||
shouldShowLoginWithDevice = false,
|
||||
)
|
||||
|
||||
private const val LOGIN_RESULT_PATH =
|
||||
|
|
Loading…
Add table
Reference in a new issue