BIT-808: Conditionally show log in with device on login (#681)

This commit is contained in:
Caleb Derosier authored and Álison Fernandes committed 2024-06-20 17:08:07 +01:00
1 parent 2fa7851b42
commit 6cbfff254c
14 files changed
+265 -14

No files matched your search

@@ -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 =