summaryrefslogtreecommitdiffhomepage
path: root/android/app
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson90@gmail.com>2023-10-13 12:21:10 +0200
committerDavid Göransson <david.goransson90@gmail.com>2023-10-23 11:28:23 +0200
commit02b7b4313323fcb1bb10f72ccb956177d44ecf16 (patch)
tree0c66180f1b208a00ecb9885d0d9f4372d578660e /android/app
parentc085b31acdc002076106a30f7cd1dcdcd43daf05 (diff)
downloadmullvadvpn-02b7b4313323fcb1bb10f72ccb956177d44ecf16.tar.xz
mullvadvpn-02b7b4313323fcb1bb10f72ccb956177d44ecf16.zip
Add tests
Diffstat (limited to 'android/app')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt67
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt102
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt75
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt81
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt94
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt114
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt91
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt4
8 files changed, 514 insertions, 114 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
index d6ef5d3311..56894addea 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreenTest.kt
@@ -13,18 +13,18 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import net.mullvad.mullvadvpn.compose.setContentWithTheme
-import net.mullvad.mullvadvpn.compose.state.ConnectNotificationState
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.compose.test.CIRCULAR_PROGRESS_INDICATOR
import net.mullvad.mullvadvpn.compose.test.CONNECT_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.LOCATION_INFO_TEST_TAG
-import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER
+import net.mullvad.mullvadvpn.compose.test.NOTIFICATION_BANNER_ACTION
import net.mullvad.mullvadvpn.compose.test.RECONNECT_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SCROLLABLE_COLUMN_TEST_TAG
import net.mullvad.mullvadvpn.compose.test.SELECT_LOCATION_BUTTON_TEST_TAG
import net.mullvad.mullvadvpn.model.GeoIpLocation
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.relaylist.RelayItem
+import net.mullvad.mullvadvpn.repository.InAppNotification
import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.viewmodel.ConnectViewModel
import net.mullvad.talpid.net.TransportProtocol
@@ -86,8 +86,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationBlocked
+ inAppNotification = InAppNotification.TunnelStateBlocked
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -123,8 +122,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationBlocked
+ inAppNotification = InAppNotification.TunnelStateBlocked
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -158,7 +156,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -191,7 +189,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -225,7 +223,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -259,7 +257,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -295,8 +293,8 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationError(
+ inAppNotification =
+ InAppNotification.TunnelStateError(
ErrorState(ErrorStateCause.StartTunnelError, true)
)
),
@@ -335,8 +333,8 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationError(
+ inAppNotification =
+ InAppNotification.TunnelStateError(
ErrorState(ErrorStateCause.StartTunnelError, false)
)
),
@@ -372,8 +370,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationBlocked
+ inAppNotification = InAppNotification.TunnelStateBlocked
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -409,8 +406,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationBlocked
+ inAppNotification = InAppNotification.TunnelStateBlocked
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -446,7 +442,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow(),
onSwitchLocationClick = mockedClickHandler
@@ -479,7 +475,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow(),
onDisconnectClick = mockedClickHandler
@@ -512,7 +508,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow(),
onReconnectClick = mockedClickHandler
@@ -544,7 +540,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow(),
onConnectClick = mockedClickHandler
@@ -576,7 +572,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow(),
onCancelClick = mockedClickHandler
@@ -609,7 +605,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow(),
onToggleTunnelInfo = mockedClickHandler
@@ -649,7 +645,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = true,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState = ConnectNotificationState.HideNotification
+ inAppNotification = null
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -688,8 +684,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowVersionInfoNotification(versionInfo)
+ inAppNotification = InAppNotification.UpdateAvailable(versionInfo)
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -726,8 +721,7 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowVersionInfoNotification(versionInfo)
+ inAppNotification = InAppNotification.UnsupportedVersion(versionInfo)
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -759,10 +753,9 @@ class ConnectScreenTest {
outAddress = "",
showLocation = false,
isTunnelInfoExpanded = false,
- deviceName = null,
+ deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowAccountExpiryNotification(expiryDate)
+ inAppNotification = InAppNotification.AccountExpiry(expiryDate)
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
@@ -801,15 +794,14 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowVersionInfoNotification(versionInfo)
+ inAppNotification = InAppNotification.UnsupportedVersion(versionInfo)
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
}
// Act
- composeTestRule.onNodeWithTag(NOTIFICATION_BANNER).performClick()
+ composeTestRule.onNodeWithTag(NOTIFICATION_BANNER_ACTION).performClick()
// Assert
verify { mockedClickHandler.invoke() }
@@ -835,15 +827,14 @@ class ConnectScreenTest {
isTunnelInfoExpanded = false,
deviceName = "",
daysLeftUntilExpiry = null,
- connectNotificationState =
- ConnectNotificationState.ShowAccountExpiryNotification(expiryDate)
+ inAppNotification = InAppNotification.AccountExpiry(expiryDate)
),
uiSideEffect = MutableSharedFlow<ConnectViewModel.UiSideEffect>().asSharedFlow()
)
}
// Act
- composeTestRule.onNodeWithTag(NOTIFICATION_BANNER).performClick()
+ composeTestRule.onNodeWithTag(NOTIFICATION_BANNER_ACTION).performClick()
// Assert
verify { mockedClickHandler.invoke() }
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
new file mode 100644
index 0000000000..30b54cea11
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
@@ -0,0 +1,102 @@
+package net.mullvad.mullvadvpn
+
+import app.cash.turbine.test
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.repository.InAppNotification
+import net.mullvad.mullvadvpn.repository.InAppNotificationController
+import net.mullvad.mullvadvpn.usecase.AccountExpiryNotificationUseCase
+import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase
+import net.mullvad.mullvadvpn.usecase.TunnelStateNotificationUseCase
+import net.mullvad.mullvadvpn.usecase.VersionNotificationUseCase
+import net.mullvad.talpid.tunnel.ErrorState
+import org.joda.time.DateTime
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class InAppNotificationControllerTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ private lateinit var inAppNotificationController: InAppNotificationController
+ private val accountExpiryNotifications = MutableStateFlow(emptyList<InAppNotification>())
+ private val newDeviceNotifications = MutableStateFlow(emptyList<InAppNotification.NewDevice>())
+ private val versionNotifications = MutableStateFlow(emptyList<InAppNotification>())
+ private val tunnelStateNotifications = MutableStateFlow(emptyList<InAppNotification>())
+
+ private lateinit var job: Job
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this)
+
+ val accountExpiryNotificationUseCase: AccountExpiryNotificationUseCase = mockk()
+ val newDeviceNotificationUseCase: NewDeviceNotificationUseCase = mockk()
+ val versionNotificationUseCase: VersionNotificationUseCase = mockk()
+ val tunnelStateNotificationUseCase: TunnelStateNotificationUseCase = mockk()
+ every { accountExpiryNotificationUseCase.notifications() } returns
+ accountExpiryNotifications
+ every { newDeviceNotificationUseCase.notifications() } returns newDeviceNotifications
+ every { versionNotificationUseCase.notifications() } returns versionNotifications
+ every { tunnelStateNotificationUseCase.notifications() } returns tunnelStateNotifications
+ job = Job()
+
+ inAppNotificationController =
+ InAppNotificationController(
+ accountExpiryNotificationUseCase,
+ newDeviceNotificationUseCase,
+ versionNotificationUseCase,
+ tunnelStateNotificationUseCase,
+ CoroutineScope(job + testCoroutineRule.testDispatcher)
+ )
+ }
+
+ @After
+ fun teardown() {
+ job.cancel()
+ unmockkAll()
+ }
+
+ @Test
+ fun `ensure all notifications have the right priority`() = runTest {
+ val newDevice = InAppNotification.NewDevice("")
+ newDeviceNotifications.value = listOf(newDevice)
+
+ val errorState: ErrorState = mockk()
+ val tunnelStateBlocked = InAppNotification.TunnelStateBlocked
+ val tunnelStateError = InAppNotification.TunnelStateError(errorState)
+ tunnelStateNotifications.value = listOf(tunnelStateBlocked, tunnelStateError)
+
+ val unsupportedVersion = InAppNotification.UnsupportedVersion(mockk())
+ val updateAvailable = InAppNotification.UpdateAvailable(mockk())
+ versionNotifications.value = listOf(unsupportedVersion, updateAvailable)
+
+ val accountExpiry = InAppNotification.AccountExpiry(DateTime.now())
+ accountExpiryNotifications.value = listOf(accountExpiry)
+
+ inAppNotificationController.notifications.test {
+ val notifications = awaitItem()
+
+ assertEquals(
+ listOf(
+ tunnelStateError,
+ tunnelStateBlocked,
+ unsupportedVersion,
+ accountExpiry,
+ newDevice,
+ updateAvailable,
+ ),
+ notifications
+ )
+ }
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt
new file mode 100644
index 0000000000..5341708d3b
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt
@@ -0,0 +1,75 @@
+package net.mullvad.mullvadvpn.usecase
+
+import app.cash.turbine.test
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.model.AccountExpiry
+import net.mullvad.mullvadvpn.repository.AccountRepository
+import net.mullvad.mullvadvpn.repository.InAppNotification
+import org.joda.time.DateTime
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class AccountExpiryNotificationUseCaseTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ private val accountExpiry = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing)
+ private lateinit var accountExpiryNotificationUseCase: AccountExpiryNotificationUseCase
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this)
+
+ val accountRepository = mockk<AccountRepository>()
+ every { accountRepository.accountExpiryState } returns accountExpiry
+
+ accountExpiryNotificationUseCase = AccountExpiryNotificationUseCase(accountRepository)
+ }
+
+ @After
+ fun teardown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun `ensure notifications are empty by default`() = runTest {
+ // Arrange, Act, Assert
+ accountExpiryNotificationUseCase.notifications().test {
+ assertTrue { awaitItem().isEmpty() }
+ }
+ }
+
+ @Test
+ fun `ensure account expiry within 3 days generates notification`() = runTest {
+ // Arrange, Act, Assert
+ accountExpiryNotificationUseCase.notifications().test {
+ assertTrue { awaitItem().isEmpty() }
+ val closeToExpiry = AccountExpiry.Available(DateTime.now().plusDays(2))
+ accountExpiry.value = closeToExpiry
+
+ assertEquals(
+ listOf(InAppNotification.AccountExpiry(closeToExpiry.expiryDateTime)),
+ awaitItem()
+ )
+ }
+ }
+
+ @Test
+ fun `ensure an expire of 4 days in the future does not produce a notification`() = runTest {
+ // Arrange, Act, Assert
+ accountExpiryNotificationUseCase.notifications().test {
+ assertTrue { awaitItem().isEmpty() }
+ accountExpiry.value = AccountExpiry.Available(DateTime.now().plusDays(4))
+ expectNoEvents()
+ }
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt
new file mode 100644
index 0000000000..bd375d729a
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt
@@ -0,0 +1,81 @@
+package net.mullvad.mullvadvpn.usecase
+
+import app.cash.turbine.test
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.model.AccountAndDevice
+import net.mullvad.mullvadvpn.model.Device
+import net.mullvad.mullvadvpn.model.DeviceState
+import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.repository.InAppNotification
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class NewDeviceUseNotificationCaseTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ private val deviceName = "Frank Zebra"
+ private val deviceState =
+ MutableStateFlow<DeviceState>(
+ DeviceState.LoggedIn(
+ accountAndDevice = AccountAndDevice("", Device("", deviceName, byteArrayOf(), ""))
+ )
+ )
+ private lateinit var newDeviceNotificationUseCase: NewDeviceNotificationUseCase
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this)
+
+ val mockDeviceRepository: DeviceRepository = mockk()
+ every { mockDeviceRepository.deviceState } returns deviceState
+ newDeviceNotificationUseCase =
+ NewDeviceNotificationUseCase(deviceRepository = mockDeviceRepository)
+ }
+
+ @After
+ fun teardown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun `ensure empty by default`() = runTest {
+ // Arrange, Act, Assert
+ newDeviceNotificationUseCase.notifications().test { assertTrue { awaitItem().isEmpty() } }
+ }
+
+ @Test
+ fun `ensure NewDevice notification is created and contains device name`() = runTest {
+ newDeviceNotificationUseCase.notifications().test {
+ // Arrange, Act
+ awaitItem()
+ newDeviceNotificationUseCase.newDeviceCreated()
+
+ // Assert
+ assertEquals(awaitItem(), listOf(InAppNotification.NewDevice(deviceName)))
+ }
+ }
+
+ @Test
+ fun `ensure NewDevice notification is cleared`() = runTest {
+ newDeviceNotificationUseCase.notifications().test {
+ // Arrange, Act
+ awaitItem()
+ newDeviceNotificationUseCase.newDeviceCreated()
+ awaitItem()
+ newDeviceNotificationUseCase.clearNewDeviceCreatedNotification()
+
+ // Assert
+ assertEquals(awaitItem(), emptyList())
+ }
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
new file mode 100644
index 0000000000..1b89c92be7
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
@@ -0,0 +1,94 @@
+package net.mullvad.mullvadvpn.usecase
+
+import app.cash.turbine.test
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.model.TunnelState
+import net.mullvad.mullvadvpn.repository.InAppNotification
+import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
+import net.mullvad.talpid.tunnel.ActionAfterDisconnect
+import net.mullvad.talpid.tunnel.ErrorState
+import net.mullvad.talpid.util.EventNotifier
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class TunnelStateNotificationUseCaseTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
+ private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+ private val mockConnectionProxy: ConnectionProxy = mockk()
+
+ private val serviceConnectionState =
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+ private lateinit var tunnelStateNotificationUseCase: TunnelStateNotificationUseCase
+
+ private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected)
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this)
+ every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState
+
+ every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
+ every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
+
+ tunnelStateNotificationUseCase =
+ TunnelStateNotificationUseCase(serviceConnectionManager = mockServiceConnectionManager)
+ }
+
+ @After
+ fun teardown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun `ensure notifications are empty by default`() = runTest {
+ // Arrange, Act, Assert
+ tunnelStateNotificationUseCase.notifications().test { assertTrue { awaitItem().isEmpty() } }
+ }
+
+ @Test
+ fun `ensure TunnelState with error will produce TunnelStateError notification`() = runTest {
+ tunnelStateNotificationUseCase.notifications().test {
+ // Arrange, Act
+ assertEquals(emptyList(), awaitItem())
+ serviceConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ val errorState: ErrorState = mockk()
+ eventNotifierTunnelUiState.notify(TunnelState.Error(errorState))
+
+ // Assert
+ assertEquals(listOf(InAppNotification.TunnelStateError(errorState)), awaitItem())
+ }
+ }
+
+ @Test
+ fun `ensure disconnecting TunnelState with blocking will produce TunnelStateBlocked notification`() =
+ runTest {
+ tunnelStateNotificationUseCase.notifications().test {
+ // Arrange, Act
+ assertEquals(emptyList(), awaitItem())
+ serviceConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ eventNotifierTunnelUiState.notify(
+ TunnelState.Disconnecting(ActionAfterDisconnect.Block)
+ )
+
+ // Assert
+ assertEquals(listOf(InAppNotification.TunnelStateBlocked), awaitItem())
+ }
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
new file mode 100644
index 0000000000..5aba70c938
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
@@ -0,0 +1,114 @@
+package net.mullvad.mullvadvpn.usecase
+
+import app.cash.turbine.test
+import io.mockk.MockKAnnotations
+import io.mockk.every
+import io.mockk.mockk
+import io.mockk.mockkStatic
+import io.mockk.unmockkAll
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.repository.InAppNotification
+import net.mullvad.mullvadvpn.ui.VersionInfo
+import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
+import net.mullvad.mullvadvpn.util.appVersionCallbackFlow
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+
+class VersionNotificationUseCaseTest {
+ @get:Rule val testCoroutineRule = TestCoroutineRule()
+
+ private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
+ private lateinit var mockAppVersionInfoCache: AppVersionInfoCache
+ private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+
+ private val serviceConnectionState =
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+ private val versionInfo =
+ MutableStateFlow(
+ VersionInfo(
+ currentVersion = null,
+ upgradeVersion = null,
+ isOutdated = false,
+ isSupported = true
+ )
+ )
+ private lateinit var versionNotificationUseCase: VersionNotificationUseCase
+
+ @Before
+ fun setup() {
+ MockKAnnotations.init(this)
+ mockkStatic(CACHE_EXTENSION_CLASS)
+ mockAppVersionInfoCache =
+ mockk<AppVersionInfoCache>().apply {
+ every { appVersionCallbackFlow() } returns versionInfo
+ }
+
+ every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
+ every { mockServiceConnectionContainer.appVersionInfoCache } returns mockAppVersionInfoCache
+ every { mockAppVersionInfoCache.onUpdate = any() } answers {}
+
+ versionNotificationUseCase =
+ VersionNotificationUseCase(
+ serviceConnectionManager = mockServiceConnectionManager,
+ isVersionInfoNotificationEnabled = true
+ )
+ }
+
+ @After
+ fun teardown() {
+ unmockkAll()
+ }
+
+ @Test
+ fun `ensure notifications are empty by default`() = runTest {
+ // Arrange, Act, Assert
+ versionNotificationUseCase.notifications().test { assertTrue { awaitItem().isEmpty() } }
+ }
+
+ @Test
+ fun `ensure UpdateAvailable notification is created`() = runTest {
+ versionNotificationUseCase.notifications().test {
+ // Arrange, Act
+ val upgradeVersionInfo =
+ VersionInfo("1.0", "1.1", isOutdated = true, isSupported = true)
+ serviceConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ awaitItem()
+ versionInfo.value = upgradeVersionInfo
+
+ // Assert
+ assertEquals(awaitItem(), listOf(InAppNotification.UpdateAvailable(upgradeVersionInfo)))
+ }
+ }
+
+ @Test
+ fun `ensure UnsupportedVersion notification is created`() = runTest {
+ versionNotificationUseCase.notifications().test {
+ // Arrange, Act
+ val upgradeVersionInfo = VersionInfo("1.0", "", isOutdated = false, isSupported = false)
+ serviceConnectionState.value =
+ ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ awaitItem()
+ versionInfo.value = upgradeVersionInfo
+
+ // Assert
+ assertEquals(
+ awaitItem(),
+ listOf(InAppNotification.UnsupportedVersion(upgradeVersionInfo))
+ )
+ }
+ }
+
+ companion object {
+ private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
index bddaee353e..5839e575c1 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt
@@ -11,12 +11,12 @@ import io.mockk.unmockkAll
import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
+import kotlin.test.assertTrue
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
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.model.AccountExpiry
@@ -27,6 +27,8 @@ import net.mullvad.mullvadvpn.relaylist.RelayCountry
import net.mullvad.mullvadvpn.relaylist.RelayItem
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.repository.InAppNotification
+import net.mullvad.mullvadvpn.repository.InAppNotificationController
import net.mullvad.mullvadvpn.ui.VersionInfo
import net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoCache
import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache
@@ -42,8 +44,6 @@ 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
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -68,6 +68,7 @@ class ConnectViewModelTest {
)
private val accountExpiryState = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing)
private val deviceState = MutableStateFlow<DeviceState>(DeviceState.Initial)
+ private val notifications = MutableStateFlow<List<InAppNotification>>(emptyList())
// Service connections
private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
@@ -83,6 +84,9 @@ class ConnectViewModelTest {
// Device Repository
private val mockDeviceRepository: DeviceRepository = mockk()
+ // In App Notifications
+ private val mockInAppNotificationController: InAppNotificationController = mockk()
+
// Captures
private val locationSlot = slot<((GeoIpLocation?) -> Unit)>()
private val relaySlot = slot<(List<RelayCountry>, RelayItem?) -> Unit>()
@@ -111,6 +115,8 @@ class ConnectViewModelTest {
every { mockDeviceRepository.deviceState } returns deviceState
+ every { mockInAppNotificationController.notifications } returns notifications
+
every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState
every { mockConnectionProxy.onStateChange } returns eventNotifierTunnelRealState
@@ -126,7 +132,8 @@ class ConnectViewModelTest {
serviceConnectionManager = mockServiceConnectionManager,
accountRepository = mockAccountRepository,
deviceRepository = mockDeviceRepository,
- isVersionInfoNotificationEnabled = true
+ inAppNotificationController = mockInAppNotificationController,
+ newDeviceNotificationUseCase = mockk()
)
}
@@ -144,8 +151,6 @@ class ConnectViewModelTest {
@Test
fun testTunnelInfoExpandedUpdate() =
runTest(testCoroutineRule.testDispatcher) {
- val expectedResult = true
-
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
serviceConnectionState.value =
@@ -154,7 +159,7 @@ class ConnectViewModelTest {
relaySlot.captured.invoke(mockk(), mockk())
viewModel.toggleTunnelInfoExpansion()
val result = awaitItem()
- assertEquals(expectedResult, result.isTunnelInfoExpanded)
+ assertTrue(result.isTunnelInfoExpanded)
}
}
@@ -288,34 +293,14 @@ class ConnectViewModelTest {
}
@Test
- fun testBlockingNotificationState() =
- runTest(testCoroutineRule.testDispatcher) {
- // Arrange
- val expectedConnectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationBlocked
- val tunnelUiState = TunnelState.Connecting(null, null)
-
- // Act, Assert
- viewModel.uiState.test {
- assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
- relaySlot.captured.invoke(mockk(), mockk())
- eventNotifierTunnelUiState.notify(tunnelUiState)
- val result = awaitItem()
- assertEquals(expectedConnectNotificationState, result.connectNotificationState)
- }
- }
-
- @Test
fun testErrorNotificationState() =
runTest(testCoroutineRule.testDispatcher) {
// Arrange
val mockErrorState: ErrorState = mockk()
val expectedConnectNotificationState =
- ConnectNotificationState.ShowTunnelStateNotificationError(mockErrorState)
+ InAppNotification.TunnelStateError(mockErrorState)
val tunnelUiState = TunnelState.Error(mockErrorState)
+ notifications.value = listOf(expectedConnectNotificationState)
// Act, Assert
viewModel.uiState.test {
@@ -326,53 +311,7 @@ class ConnectViewModelTest {
relaySlot.captured.invoke(mockk(), mockk())
eventNotifierTunnelUiState.notify(tunnelUiState)
val result = awaitItem()
- assertEquals(expectedConnectNotificationState, result.connectNotificationState)
- }
- }
-
- @Test
- fun testVersionInfoNotificationState() =
- runTest(testCoroutineRule.testDispatcher) {
- // Arrange
- val mockVersionInfo: VersionInfo = mockk()
- val expectedConnectNotificationState =
- ConnectNotificationState.ShowVersionInfoNotification(mockVersionInfo)
- every { mockVersionInfo.isOutdated } returns true
-
- // Act, Assert
- viewModel.uiState.test {
- assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
- relaySlot.captured.invoke(mockk(), mockk())
- versionInfo.value = mockVersionInfo
- val result = awaitItem()
- assertEquals(expectedConnectNotificationState, result.connectNotificationState)
- }
- }
-
- @Test
- fun testAccountExpiryNotificationState() =
- runTest(testCoroutineRule.testDispatcher) {
- // Arrange
- val mockDateTime: DateTime = mockk()
- val expectedConnectNotificationState =
- ConnectNotificationState.ShowAccountExpiryNotification(mockDateTime)
- every { mockDateTime.isBefore(any<ReadableInstant>()) } returns true
- every { mockDateTime.toInstant().millis } returns 0
-
- // Act, Assert
- viewModel.uiState.test {
- assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- locationSlot.captured.invoke(mockLocation)
- relaySlot.captured.invoke(mockk(), mockk())
- accountExpiryState.value = AccountExpiry.Available(mockDateTime)
-
- val result = awaitItem()
- assertEquals(expectedConnectNotificationState, result.connectNotificationState)
+ assertEquals(expectedConnectNotificationState, result.inAppNotification)
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt
index 744989a922..2ada5bf767 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt
@@ -24,6 +24,7 @@ import net.mullvad.mullvadvpn.model.DeviceListEvent
import net.mullvad.mullvadvpn.model.LoginResult
import net.mullvad.mullvadvpn.repository.AccountRepository
import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
@@ -34,6 +35,7 @@ class LoginViewModelTest {
@MockK private lateinit var mockedAccountRepository: AccountRepository
@MockK private lateinit var mockedDeviceRepository: DeviceRepository
+ @MockK private lateinit var mockedNewDeviceNotificationUseCase: NewDeviceNotificationUseCase
private lateinit var loginViewModel: LoginViewModel
private val accountHistoryTestEvents = MutableStateFlow<AccountHistory>(AccountHistory.Missing)
@@ -44,11 +46,13 @@ class LoginViewModelTest {
MockKAnnotations.init(this, relaxUnitFun = true)
every { mockedAccountRepository.accountHistory } returns accountHistoryTestEvents
+ every { mockedNewDeviceNotificationUseCase.newDeviceCreated() } returns Unit
loginViewModel =
LoginViewModel(
mockedAccountRepository,
mockedDeviceRepository,
+ mockedNewDeviceNotificationUseCase,
UnconfinedTestDispatcher()
)
}