Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move out of time behaviour to view model #5049

Merged
merged 4 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Line wrap the file at 100 chars. Th
#### Android
- Migrate welcome view to compose.
- Migrate in app notifications to compose.
- Move out of time evaluation to connect view model.

#### Linux
- Don't block forwarding of traffic when the split tunnel mark (ct mark) is set.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -820,4 +820,20 @@ class ConnectScreenTest {
// Assert
composeTestRule.apply { onNodeWithTag(SCROLLABLE_COLUMN_TEST_TAG).assertDoesNotExist() }
}

@Test
fun testOpenOutOfTimeScreen() {
// Arrange
val mockedOpenScreenHandler: () -> Unit = mockk(relaxed = true)
composeTestRule.setContent {
ConnectScreen(
uiState = ConnectUiState.INITIAL,
viewActions = MutableStateFlow(ConnectViewModel.ViewAction.OpenOutOfTimeView),
onOpenOutOfTimeScreen = mockedOpenScreenHandler
)
}

// Assert
verify { mockedOpenScreenHandler.invoke() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,19 @@ fun ConnectScreen(
onSwitchLocationClick: () -> Unit = {},
onToggleTunnelInfo: () -> Unit = {},
onUpdateVersionClick: () -> Unit = {},
onManageAccountClick: () -> Unit = {}
onManageAccountClick: () -> Unit = {},
onOpenOutOfTimeScreen: () -> Unit = {}
) {
val context = LocalContext.current
LaunchedEffect(key1 = Unit) {
viewActions.collect { viewAction ->
if (viewAction is ConnectViewModel.ViewAction.OpenAccountManagementPageInBrowser) {
context.openAccountPageInBrowser(viewAction.token)
when (viewAction) {
is ConnectViewModel.ViewAction.OpenAccountManagementPageInBrowser -> {
context.openAccountPageInBrowser(viewAction.token)
}
is ConnectViewModel.ViewAction.OpenOutOfTimeView -> {
onOpenOutOfTimeScreen()
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import net.mullvad.mullvadvpn.ui.NavigationBarPainter
import net.mullvad.mullvadvpn.ui.paintNavigationBar
import net.mullvad.mullvadvpn.ui.widget.HeaderBar
import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import net.mullvad.talpid.tunnel.ErrorStateCause
import org.koin.androidx.viewmodel.ext.android.viewModel

class ConnectFragment : BaseFragment(), NavigationBarPainter {
Expand Down Expand Up @@ -66,7 +65,8 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter {
onSwitchLocationClick = { openSwitchLocationScreen() },
onToggleTunnelInfo = connectViewModel::toggleTunnelInfoExpansion,
onUpdateVersionClick = { openDownloadUrl() },
onManageAccountClick = connectViewModel::onManageAccountClick
onManageAccountClick = connectViewModel::onManageAccountClick,
onOpenOutOfTimeScreen = ::openOutOfTimeScreen
)
}
}
Expand Down Expand Up @@ -103,10 +103,6 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter {

private fun updateTunnelState(realState: TunnelState) {
headerBar.tunnelState = realState

if (realState.isTunnelErrorStateDueToExpiredAccount()) {
openOutOfTimeScreen()
}
}

private fun openSwitchLocationScreen() {
Expand All @@ -124,17 +120,9 @@ class ConnectFragment : BaseFragment(), NavigationBarPainter {
}

private fun openOutOfTimeScreen() {
jobTracker.newUiJob("openOutOfTimeScreen") {
parentFragmentManager.beginTransaction().apply {
replace(R.id.main_fragment, OutOfTimeFragment())
commitAllowingStateLoss()
}
parentFragmentManager.beginTransaction().apply {
replace(R.id.main_fragment, OutOfTimeFragment())
commitAllowingStateLoss()
}
}

private fun TunnelState.isTunnelErrorStateDueToExpiredAccount(): Boolean {
return ((this as? TunnelState.Error)?.errorState?.cause as? ErrorStateCause.AuthFailed)
?.isCausedByExpiredAccount()
?: false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import net.mullvad.mullvadvpn.util.combine
import net.mullvad.mullvadvpn.util.toInAddress
import net.mullvad.mullvadvpn.util.toOutAddress
import net.mullvad.talpid.tunnel.ActionAfterDisconnect
import net.mullvad.talpid.tunnel.ErrorStateCause
import org.joda.time.DateTime

@OptIn(FlowPreview::class)
Expand Down Expand Up @@ -83,6 +84,9 @@ class ConnectViewModel(
tunnelRealState,
accountExpiry,
isTunnelInfoExpanded ->
if (tunnelRealState.isTunnelErrorStateDueToExpiredAccount()) {
_viewActions.tryEmit(ViewAction.OpenOutOfTimeView)
}
ConnectUiState(
location =
when (tunnelRealState) {
Expand Down Expand Up @@ -178,6 +182,12 @@ class ConnectViewModel(
return this.date()?.isBefore(threeDaysFromNow) == true
}

private fun TunnelState.isTunnelErrorStateDueToExpiredAccount(): Boolean {
return ((this as? TunnelState.Error)?.errorState?.cause as? ErrorStateCause.AuthFailed)
?.isCausedByExpiredAccount()
?: false
}

fun toggleTunnelInfoExpansion() {
_isTunnelInfoExpanded.value = _isTunnelInfoExpanded.value.not()
}
Expand Down Expand Up @@ -210,6 +220,8 @@ class ConnectViewModel(

sealed interface ViewAction {
data class OpenAccountManagementPageInBrowser(val token: String) : ViewAction

data object OpenOutOfTimeView : ViewAction
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import io.mockk.unmockkAll
import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.ConnectNotificationState
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
Expand All @@ -36,6 +38,7 @@ import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
import net.mullvad.mullvadvpn.util.appVersionCallbackFlow
import net.mullvad.talpid.tunnel.ErrorState
import net.mullvad.talpid.tunnel.ErrorStateCause
import net.mullvad.talpid.util.EventNotifier
import org.joda.time.DateTime
import org.joda.time.ReadableInstant
Expand Down Expand Up @@ -404,6 +407,29 @@ class ConnectViewModelTest {
}
}

@Test
fun testOutOfTimeViewAction() =
runTest(testCoroutineRule.testDispatcher) {
// Arrange
val errorStateCause = ErrorStateCause.AuthFailed("[EXPIRED_ACCOUNT]")
val tunnelRealStateTestItem = TunnelState.Error(ErrorState(errorStateCause, true))
val deferred = async { viewModel.viewActions.first() }

// Act
viewModel.uiState.test {
awaitItem()
serviceConnectionState.value =
ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
locationSlot.captured.invoke(mockLocation)
relaySlot.captured.invoke(mockk(), mockk())
eventNotifierTunnelRealState.notify(tunnelRealStateTestItem)
awaitItem()
}

// Assert
assertIs<ConnectViewModel.ViewAction.OpenOutOfTimeView>(deferred.await())
}

companion object {
private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS =
Expand Down