mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 12:00:19 +03:00
BIT-1286: Check for Internet connection before logging in via SSO (#695)
This commit is contained in:
parent
6dd4a31a57
commit
49b4c23466
8 changed files with 161 additions and 4 deletions
|
@ -0,0 +1,12 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
/**
|
||||
* Manager to detect and handle changes to network connectivity.
|
||||
*/
|
||||
interface NetworkConnectionManager {
|
||||
/**
|
||||
* Returns `true` if the application has a network connection and access to the Internet is
|
||||
* available.
|
||||
*/
|
||||
val isNetworkConnected: Boolean
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
|
||||
/**
|
||||
* Primary implementation of [NetworkConnectionManager].
|
||||
*/
|
||||
class NetworkConnectionManagerImpl(
|
||||
context: Context,
|
||||
) : NetworkConnectionManager {
|
||||
private val connectivityManager: ConnectivityManager = context
|
||||
.applicationContext
|
||||
.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
override val isNetworkConnected: Boolean
|
||||
get() = connectivityManager
|
||||
.getNetworkCapabilities(connectivityManager.activeNetwork)
|
||||
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
?: false
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.di
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
|
||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||
|
@ -12,6 +13,8 @@ import com.x8bit.bitwarden.data.platform.manager.AppForegroundManager
|
|||
import com.x8bit.bitwarden.data.platform.manager.AppForegroundManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConfigManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManager
|
||||
import com.x8bit.bitwarden.data.platform.manager.PushManagerImpl
|
||||
import com.x8bit.bitwarden.data.platform.manager.SdkClientManager
|
||||
|
@ -79,6 +82,14 @@ object PlatformManagerModule {
|
|||
dispatcherManager = dispatcherManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNetworkConnectionManager(
|
||||
application: Application,
|
||||
): NetworkConnectionManager = NetworkConnectionManagerImpl(
|
||||
context = application.applicationContext,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providePushManager(
|
||||
|
|
|
@ -68,7 +68,7 @@ fun EnterpriseSignOnScreen(
|
|||
is EnterpriseSignOnState.DialogState.Error -> {
|
||||
BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
title = dialog.title ?: R.string.an_error_has_occurred.asText(),
|
||||
message = dialog.message,
|
||||
),
|
||||
onDismissRequest = remember(viewModel) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.enterprisesignon
|
|||
import android.os.Parcelable
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
@ -18,6 +19,7 @@ private const val KEY_STATE = "state"
|
|||
*/
|
||||
@HiltViewModel
|
||||
class EnterpriseSignOnViewModel @Inject constructor(
|
||||
private val networkConnectionManager: NetworkConnectionManager,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : BaseViewModel<EnterpriseSignOnState, EnterpriseSignOnEvent, EnterpriseSignOnAction>(
|
||||
initialState = savedStateHandle[KEY_STATE]
|
||||
|
@ -50,11 +52,23 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
|||
// TODO BIT-816: submit request for single sign on
|
||||
sendEvent(EnterpriseSignOnEvent.ShowToast("Not yet implemented."))
|
||||
|
||||
if (!networkConnectionManager.isNetworkConnected) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Error(
|
||||
title = R.string.internet_connection_required_title.asText(),
|
||||
message = R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (state.orgIdentifierInput.isBlank()) {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Error(
|
||||
R.string.validation_field_required.asText(
|
||||
message = R.string.validation_field_required.asText(
|
||||
R.string.org_identifier.asText(),
|
||||
),
|
||||
),
|
||||
|
@ -83,10 +97,12 @@ data class EnterpriseSignOnState(
|
|||
*/
|
||||
sealed class DialogState : Parcelable {
|
||||
/**
|
||||
* Represents an error dialog with the given [message].
|
||||
* Represents an error dialog with the given [message] and optional [title]. It no title
|
||||
* is specified a default will be provided.
|
||||
*/
|
||||
@Parcelize
|
||||
data class Error(
|
||||
val title: Text? = null,
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager
|
||||
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.Test
|
||||
import org.junit.jupiter.api.Assertions.assertFalse
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
class NetworkConnectionManagerTest {
|
||||
@Test
|
||||
fun `isNetworkConnected should return false if no active network`() {
|
||||
val connectivityManager: ConnectivityManager = mockk {
|
||||
every { activeNetwork } returns null
|
||||
every { getNetworkCapabilities(any()) } returns null
|
||||
}
|
||||
val networkConnectionManager = createNetworkConnectionManager(connectivityManager)
|
||||
assertFalse(networkConnectionManager.isNetworkConnected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isNetworkConnected should return false if active network has no Internet capabilities`() {
|
||||
val network: Network = mockk()
|
||||
val networkCapabilities: NetworkCapabilities = mockk {
|
||||
every { hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } returns false
|
||||
}
|
||||
val connectivityManager: ConnectivityManager = mockk {
|
||||
every { activeNetwork } returns network
|
||||
every { getNetworkCapabilities(network) } returns networkCapabilities
|
||||
}
|
||||
val networkConnectionManager = createNetworkConnectionManager(connectivityManager)
|
||||
assertFalse(networkConnectionManager.isNetworkConnected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `isNetworkConnected should return true if active network has Internet capabilities`() {
|
||||
val network: Network = mockk()
|
||||
val networkCapabilities: NetworkCapabilities = mockk {
|
||||
every { hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } returns true
|
||||
}
|
||||
val connectivityManager: ConnectivityManager = mockk {
|
||||
every { activeNetwork } returns network
|
||||
every { getNetworkCapabilities(network) } returns networkCapabilities
|
||||
}
|
||||
val networkConnectionManager = createNetworkConnectionManager(connectivityManager)
|
||||
assertTrue(networkConnectionManager.isNetworkConnected)
|
||||
}
|
||||
|
||||
private fun createNetworkConnectionManager(
|
||||
connectivityManager: ConnectivityManager,
|
||||
): NetworkConnectionManager {
|
||||
val appContext: Context = mockk {
|
||||
every { getSystemService(Context.CONNECTIVITY_SERVICE) } returns connectivityManager
|
||||
}
|
||||
val context: Context = mockk {
|
||||
every { applicationContext } returns appContext
|
||||
}
|
||||
|
||||
return NetworkConnectionManagerImpl(context)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.x8bit.bitwarden.data.platform.manager.util
|
||||
|
||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager
|
||||
|
||||
class FakeNetworkConnectionManager(
|
||||
override val isNetworkConnected: Boolean,
|
||||
) : NetworkConnectionManager
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.auth.feature.enterprisesignon
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import app.cash.turbine.test
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.manager.util.FakeNetworkConnectionManager
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -67,7 +68,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Error(
|
||||
R.string.validation_field_required.asText(
|
||||
message = R.string.validation_field_required.asText(
|
||||
R.string.org_identifier.asText(),
|
||||
),
|
||||
),
|
||||
|
@ -81,6 +82,28 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `LogInClick with no Internet should emit ShowToast and show error dialog`() = runTest {
|
||||
val viewModel = createViewModel(isNetworkConnected = false)
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.actionChannel.trySend(EnterpriseSignOnAction.LogInClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialogState = EnterpriseSignOnState.DialogState.Error(
|
||||
title = R.string.internet_connection_required_title.asText(),
|
||||
message = R.string.internet_connection_required_message.asText(),
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
assertEquals(
|
||||
EnterpriseSignOnEvent.ShowToast("Not yet implemented."),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `OrgIdentifierInputChange should update organization identifier`() = runTest {
|
||||
val input = "input"
|
||||
|
@ -141,7 +164,9 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
|||
savedStateHandle: SavedStateHandle = SavedStateHandle(
|
||||
initialState = mapOf("state" to initialState),
|
||||
),
|
||||
isNetworkConnected: Boolean = true,
|
||||
): EnterpriseSignOnViewModel = EnterpriseSignOnViewModel(
|
||||
networkConnectionManager = FakeNetworkConnectionManager(isNetworkConnected),
|
||||
savedStateHandle = savedStateHandle,
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in a new issue