summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/test
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
committerDavid Göransson <david.goransson@mullvad.net>2024-05-29 17:18:29 +0200
commitad90145a5d86d8c1e6e70f2238f11edf5e50f8d8 (patch)
tree9d085bc81caed9409e3a4360490c06c2da4fbba8 /android/app/src/test
parent8e14a8d4287af66a57a98db79d3ac320c2dad4a1 (diff)
parent767b97eda756f4ec4e67fb5fa2ae664277291e8f (diff)
downloadmullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.tar.xz
mullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.zip
Merge branch 'android-grpc'
Diffstat (limited to 'android/app/src/test')
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt256
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt323
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/RelayListFilterRepositoryTest.kt174
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt82
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt70
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt14
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt178
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt23
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt71
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt35
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt52
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt52
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt25
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModelTest.kt210
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModelTest.kt53
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt159
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListsViewModelTest.kt14
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModelTest.kt10
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt101
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModelTest.kt34
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt35
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt85
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModelTest.kt93
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt79
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ResetServerIpOverridesConfirmationViewModelTest.kt12
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt178
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt37
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt69
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt106
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt71
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt68
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt81
33 files changed, 1134 insertions, 1718 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
index 9767d3930a..c9cfb0e75c 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/InAppNotificationControllerTest.kt
@@ -12,13 +12,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.lib.model.ErrorState
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.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt
deleted file mode 100644
index eb66c2d4f9..0000000000
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/relaylist/RelayNameComparatorTest.kt
+++ /dev/null
@@ -1,256 +0,0 @@
-package net.mullvad.mullvadvpn.relaylist
-
-import io.mockk.mockk
-import io.mockk.unmockkAll
-import net.mullvad.mullvadvpn.model.Ownership
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.Assertions.assertTrue
-import org.junit.jupiter.api.Test
-
-class RelayNameComparatorTest {
-
- @AfterEach
- fun tearDown() {
- unmockkAll()
- }
-
- @Test
- fun `given two relays with same prefix but different numbers comparator should return lowest number first`() {
- val relay9 =
- RelayItem.Relay(
- name = "se9-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay10 =
- RelayItem.Relay(
- name = "se10-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- relay9 assertOrderBothDirection relay10
- }
-
- @Test
- fun `given two relays with same name with number in name comparator should return 0`() {
- val relay9a =
- RelayItem.Relay(
- name = "se9-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay9b =
- RelayItem.Relay(
- name = "se9-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
- assertTrue(RelayNameComparator.compare(relay9b, relay9a) == 0)
- }
-
- @Test
- fun `comparator should be able to handle name of only numbers`() {
- val relay001 =
- RelayItem.Relay(
- name = "001",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay1 =
- RelayItem.Relay(
- name = "1",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay3 =
- RelayItem.Relay(
- name = "3",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay100 =
- RelayItem.Relay(
- name = "100",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- relay001 assertOrderBothDirection relay1
- relay001 assertOrderBothDirection relay3
- relay1 assertOrderBothDirection relay3
- relay3 assertOrderBothDirection relay100
- }
-
- @Test
- fun `given two relays with same name and without number comparator should return 0`() {
- val relay9a =
- RelayItem.Relay(
- name = "se-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay9b =
- RelayItem.Relay(
- name = "se-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
- assertTrue(RelayNameComparator.compare(relay9b, relay9a) == 0)
- }
-
- @Test
- fun `given two relays with leading zeroes comparator should return lowest number first`() {
- val relay001 =
- RelayItem.Relay(
- name = "se001-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay005 =
- RelayItem.Relay(
- name = "se005-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- relay001 assertOrderBothDirection relay005
- }
-
- @Test
- fun `given 4 relays comparator should sort by prefix then number`() {
- val relayAr2 =
- RelayItem.Relay(
- name = "ar2-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relayAr8 =
- RelayItem.Relay(
- name = "ar8-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relaySe5 =
- RelayItem.Relay(
- name = "se5-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relaySe10 =
- RelayItem.Relay(
- name = "se10-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- relayAr2 assertOrderBothDirection relayAr8
- relayAr8 assertOrderBothDirection relaySe5
- relaySe5 assertOrderBothDirection relaySe10
- }
-
- @Test
- fun `given two relays with same prefix and number comparator should sort by suffix`() {
- val relay2c =
- RelayItem.Relay(
- name = "se2-cloud",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay2w =
- RelayItem.Relay(
- name = "se2-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- relay2c assertOrderBothDirection relay2w
- }
-
- @Test
- fun `given two relays with same prefix, but one with no suffix, the one with no suffix should come first`() {
- val relay22a =
- RelayItem.Relay(
- name = "se22",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
- val relay22b =
- RelayItem.Relay(
- name = "se22-wireguard",
- location = mockk(),
- locationName = "mock",
- active = false,
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
- )
-
- relay22a assertOrderBothDirection relay22b
- }
-
- private infix fun RelayItem.Relay.assertOrderBothDirection(other: RelayItem.Relay) {
- assertTrue(RelayNameComparator.compare(this, other) < 0)
- assertTrue(RelayNameComparator.compare(other, this) > 0)
- }
-}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt
index 9c2ac615c3..4b8a524e5c 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/CustomListsRepositoryTest.kt
@@ -1,271 +1,236 @@
package net.mullvad.mullvadvpn.repository
+import arrow.core.left
+import arrow.core.right
+import io.mockk.coEvery
+import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
-import io.mockk.verify
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.MessageHandler
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import net.mullvad.mullvadvpn.lib.ipc.events
-import net.mullvad.mullvadvpn.model.CreateCustomListResult
-import net.mullvad.mullvadvpn.model.CustomList
-import net.mullvad.mullvadvpn.model.CustomListName
-import net.mullvad.mullvadvpn.model.CustomListsError
-import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
-import net.mullvad.mullvadvpn.model.RelayList
-import net.mullvad.mullvadvpn.model.Settings
-import net.mullvad.mullvadvpn.model.UpdateCustomListResult
-import net.mullvad.mullvadvpn.relaylist.getGeographicLocationConstraintByCode
-import net.mullvad.mullvadvpn.ui.serviceconnection.RelayListListener
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListAlreadyExists
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.GetCustomListError
+import net.mullvad.mullvadvpn.lib.model.NameAlreadyExists
+import net.mullvad.mullvadvpn.lib.model.Settings
import org.junit.jupiter.api.Assertions.assertEquals
-import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class CustomListsRepositoryTest {
- private val mockMessageHandler: MessageHandler = mockk()
- private val mockSettingsRepository: SettingsRepository = mockk()
- private val mockRelayListListener: RelayListListener = mockk()
- private val customListsRepository =
- CustomListsRepository(
- messageHandler = mockMessageHandler,
- settingsRepository = mockSettingsRepository,
- relayListListener = mockRelayListListener
- )
+ private val mockManagementService: ManagementService = mockk()
+ private lateinit var customListsRepository: CustomListsRepository
- private val settingsFlow: MutableStateFlow<Settings?> = MutableStateFlow(null)
- private val relayListFlow: MutableStateFlow<RelayList> = MutableStateFlow(mockk())
+ private val settingsFlow: MutableStateFlow<Settings> = MutableStateFlow(mockk(relaxed = true))
@BeforeEach
fun setup() {
mockkStatic(RELAY_LIST_EXTENSIONS)
- every { mockSettingsRepository.settingsUpdates } returns settingsFlow
- every { mockRelayListListener.relayListEvents } returns relayListFlow
+ every { mockManagementService.settings } returns settingsFlow
+ customListsRepository =
+ CustomListsRepository(
+ managementService = mockManagementService,
+ dispatcher = UnconfinedTestDispatcher()
+ )
}
@Test
- fun `get custom list by id should return custom list when id matches custom list in settings`() {
- // Arrange
- val mockCustomList: CustomList = mockk()
- val mockSettings: Settings = mockk()
- val customListId = "1"
- settingsFlow.value = mockSettings
- every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList)
- every { mockCustomList.id } returns customListId
+ fun `get custom list by id should return custom list when id matches custom list in settings`() =
+ runTest {
+ // Arrange
+ val customListId = CustomListId("1")
+ val mockCustomList =
+ CustomList(
+ id = customListId,
+ name = mockk(relaxed = true),
+ locations = mockk(relaxed = true)
+ )
+ val mockSettings: Settings = mockk()
+ every { mockSettings.customLists } returns listOf(mockCustomList)
+ settingsFlow.value = mockSettings
- // Act
- val result = customListsRepository.getCustomListById(customListId)
+ // Act
+ val result = customListsRepository.getCustomListById(customListId)
- // Assert
- assertEquals(mockCustomList, result)
- }
+ // Assert
+ assertEquals(mockCustomList, result.getOrNull())
+ }
+
+ @Test
+ fun `get custom list by id should return get custom list error when id does not matches custom list in settings`() =
+ runTest {
+ // Arrange
+ val customListId = CustomListId("1")
+ val mockCustomList =
+ CustomList(
+ id = customListId,
+ name = mockk(relaxed = true),
+ locations = mockk(relaxed = true)
+ )
+ val mockSettings: Settings = mockk()
+ val otherCustomListId = CustomListId("2")
+ every { mockSettings.customLists } returns listOf(mockCustomList)
+ settingsFlow.value = mockSettings
+
+ // Act
+ val result = customListsRepository.getCustomListById(otherCustomListId)
+
+ // Assert
+ assertEquals(GetCustomListError(otherCustomListId), result.leftOrNull())
+ }
@Test
- fun `get custom list by id should return null when id does not matches custom list in settings`() {
+ fun `create custom list should return id when creation is successful`() = runTest {
// Arrange
- val mockCustomList: CustomList = mockk()
- val mockSettings: Settings = mockk()
- val customListId = "1"
- val otherCustomListId = "2"
- settingsFlow.value = mockSettings
- every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList)
- every { mockCustomList.id } returns customListId
+ val customListId = CustomListId("1")
+ val expectedResult = customListId.right()
+ val customListName = CustomListName.fromString("CUSTOM")
+ coEvery { mockManagementService.createCustomList(customListName) } returns expectedResult
// Act
- val result = customListsRepository.getCustomListById(otherCustomListId)
+ val result = customListsRepository.createCustomList(customListName)
// Assert
- assertNull(result)
+ assertEquals(expectedResult, result)
}
@Test
- fun `create custom list should return Ok when creation is successful`() = runTest {
+ fun `create custom list should return lists exists error from management service`() = runTest {
// Arrange
- val customListId = "1"
- val expectedResult = CreateCustomListResult.Ok(customListId)
- val customListName = "CUSTOM"
- every {
- mockMessageHandler.trySendRequest(Request.CreateCustomList(customListName))
- } returns true
- every { mockMessageHandler.events<Event.CreateCustomListResultEvent>() } returns
- flowOf(Event.CreateCustomListResultEvent(expectedResult))
+ val expectedResult = CustomListAlreadyExists.left()
+ val customListName = CustomListName.fromString("CUSTOM")
+ coEvery { mockManagementService.createCustomList(customListName) } returns expectedResult
// Act
- val result =
- customListsRepository.createCustomList(CustomListName.fromString(customListName))
+ val result = customListsRepository.createCustomList(customListName)
// Assert
assertEquals(expectedResult, result)
}
@Test
- fun `create custom list should return lists exists when lists exists error event is received`() =
+ fun `update custom list name should return success when call ManagementService is successful`() =
runTest {
// Arrange
- val expectedResult = CreateCustomListResult.Error(CustomListsError.CustomListExists)
- val customListName = "CUSTOM"
- every {
- mockMessageHandler.trySendRequest(Request.CreateCustomList(customListName))
- } returns true
- every { mockMessageHandler.events<Event.CreateCustomListResultEvent>() } returns
- flowOf(Event.CreateCustomListResultEvent(expectedResult))
+ val customListId = CustomListId("1")
+ val expectedResult = Unit.right()
+ val customListName = CustomListName.fromString("CUSTOM")
+ val mockSettings: Settings = mockk()
+ val mockCustomList =
+ CustomList(
+ id = customListId,
+ name = mockk(relaxed = true),
+ locations = mockk(relaxed = true)
+ )
+ every { mockSettings.customLists } returns listOf(mockCustomList)
+ settingsFlow.value = mockSettings
+ coEvery { mockManagementService.updateCustomList(any<CustomList>()) } returns
+ expectedResult
// Act
- val result =
- customListsRepository.createCustomList(CustomListName.fromString(customListName))
+ val result = customListsRepository.updateCustomListName(customListId, customListName)
// Assert
assertEquals(expectedResult, result)
}
@Test
- fun `update custom list name should return ok when list updated event is received`() = runTest {
- // Arrange
- val customListId = "1"
- val expectedResult = UpdateCustomListResult.Ok
- val customListName = "CUSTOM"
- val mockSettings: Settings = mockk()
- val mockCustomList: CustomList = mockk()
- val updatedCustomList: CustomList = mockk()
- settingsFlow.value = mockSettings
- every { mockCustomList.id } returns customListId
- every { mockCustomList.copy(customListId, customListName, any()) } returns updatedCustomList
- every {
- mockMessageHandler.trySendRequest(Request.UpdateCustomList(updatedCustomList))
- } returns true
- every { mockMessageHandler.events<Event.UpdateCustomListResultEvent>() } returns
- flowOf(Event.UpdateCustomListResultEvent(expectedResult))
- every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList)
-
- // Act
- val result =
- customListsRepository.updateCustomListName(
- customListId,
- CustomListName.fromString(customListName)
- )
-
- // Assert
- assertEquals(expectedResult, result)
- }
-
- @Test
fun `update custom list name should return list exists error when list exists error is received`() =
runTest {
// Arrange
- val customListId = "1"
- val expectedResult = UpdateCustomListResult.Error(CustomListsError.CustomListExists)
- val customListName = "CUSTOM"
+ val customListId = CustomListId("1")
+ val customListName = CustomListName.fromString("CUSTOM")
+ val expectedResult = NameAlreadyExists(customListName.value).left()
val mockSettings: Settings = mockk()
- val mockCustomList: CustomList = mockk()
- val updatedCustomList: CustomList = mockk()
+ val mockCustomList =
+ CustomList(
+ id = customListId,
+ name = CustomListName.fromString("OLD CUSTOM"),
+ locations = emptyList()
+ )
+ val updatedCustomList =
+ CustomList(id = customListId, name = customListName, locations = emptyList())
+ every { mockSettings.customLists } returns listOf(mockCustomList)
settingsFlow.value = mockSettings
- every { mockCustomList.id } returns customListId
- every { mockCustomList.copy(customListId, customListName, any()) } returns
- updatedCustomList
- every {
- mockMessageHandler.trySendRequest(Request.UpdateCustomList(updatedCustomList))
- } returns true
- every { mockMessageHandler.events<Event.UpdateCustomListResultEvent>() } returns
- flowOf(Event.UpdateCustomListResultEvent(expectedResult))
- every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList)
+ coEvery { mockManagementService.updateCustomList(updatedCustomList) } returns
+ expectedResult
// Act
- val result =
- customListsRepository.updateCustomListName(
- customListId,
- CustomListName.fromString(customListName)
- )
+ val result = customListsRepository.updateCustomListName(customListId, customListName)
// Assert
assertEquals(expectedResult, result)
}
@Test
- fun `when delete custom lists is called a delete custom event should be sent`() = runTest {
- // Arrange
- val customListId = "1"
- every { mockMessageHandler.trySendRequest(Request.DeleteCustomList(customListId)) } returns
- true
+ fun `when delete custom lists is called Managementservice delete custom list should be called`() =
+ runTest {
+ // Arrange
+ val customListId = CustomListId("1")
+ coEvery { mockManagementService.deleteCustomList(customListId) } returns Unit.right()
- // Act
- customListsRepository.deleteCustomList(customListId)
+ // Act
+ customListsRepository.deleteCustomList(customListId)
- // Assert
- verify { mockMessageHandler.trySendRequest(Request.DeleteCustomList(customListId)) }
- }
+ // Assert
+ coVerify { mockManagementService.deleteCustomList(customListId) }
+ }
@Test
- fun `update custom list locations should return ok when list exists and ok updated list event is received`() =
+ fun `update custom list locations should return successful when list exists and update is successful`() =
runTest {
// Arrange
- val expectedResult = UpdateCustomListResult.Ok
- val customListId = "1"
- val customListName = "CUSTOM"
- val locationCode = "AB"
+ val expectedResult = Unit.right()
+ val customListId = CustomListId("1")
+ val customListName = CustomListName.fromString("CUSTOM")
+ val location = GeoLocationId.Country("se")
val mockSettings: Settings = mockk()
- val mockRelayList: RelayList = mockk()
- val mockCustomList: CustomList = mockk()
- val updatedCustomList: CustomList = mockk()
- val mockLocationConstraint: GeographicLocationConstraint = mockk()
+ val mockCustomList =
+ CustomList(id = customListId, name = customListName, locations = emptyList())
+ val updatedCustomList =
+ CustomList(id = customListId, name = customListName, locations = listOf(location))
+ every { mockSettings.customLists } returns listOf(mockCustomList)
settingsFlow.value = mockSettings
- relayListFlow.value = mockRelayList
- every { mockCustomList.id } returns customListId
- every { mockCustomList.name } returns customListName
- every {
- mockCustomList.copy(
- customListId,
- customListName,
- arrayListOf(mockLocationConstraint)
- )
- } returns updatedCustomList
- every {
- mockMessageHandler.trySendRequest(Request.UpdateCustomList(updatedCustomList))
- } returns true
- every { mockMessageHandler.events<Event.UpdateCustomListResultEvent>() } returns
- flowOf(Event.UpdateCustomListResultEvent(expectedResult))
- every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList)
- every { mockRelayList.getGeographicLocationConstraintByCode(locationCode) } returns
- mockLocationConstraint
+ coEvery { mockManagementService.updateCustomList(updatedCustomList) } returns
+ Unit.right()
// Act
val result =
- customListsRepository.updateCustomListLocationsFromCodes(
- customListId,
- listOf(locationCode)
- )
+ customListsRepository.updateCustomListLocations(customListId, listOf(location))
// Assert
assertEquals(expectedResult, result)
}
@Test
- fun `update custom list locations should return other error when list does not exist`() =
+ fun `update custom list locations should return get custom list error when list does not exist`() =
runTest {
// Arrange
- val expectedResult = UpdateCustomListResult.Error(CustomListsError.OtherError)
- val mockCustomList: CustomList = mockk()
val mockSettings: Settings = mockk()
- val customListId = "1"
- val otherCustomListId = "2"
- val locationCode = "AB"
- val mockRelayList: RelayList = mockk()
- val mockLocationConstraint: GeographicLocationConstraint = mockk()
+ val customListId = CustomListId("1")
+ val otherCustomListId = CustomListId("2")
+ val expectedResult = GetCustomListError(otherCustomListId).left()
+ val mockCustomList =
+ CustomList(
+ id = customListId,
+ name = CustomListName.fromString("name"),
+ locations = emptyList()
+ )
+ val locationId = GeoLocationId.Country("se")
+ every { mockSettings.customLists } returns listOf(mockCustomList)
settingsFlow.value = mockSettings
- relayListFlow.value = mockRelayList
- every { mockSettings.customLists.customLists } returns arrayListOf(mockCustomList)
- every { mockCustomList.id } returns customListId
- every { mockRelayList.getGeographicLocationConstraintByCode(locationCode) } returns
- mockLocationConstraint
// Act
val result =
- customListsRepository.updateCustomListLocationsFromCodes(
+ customListsRepository.updateCustomListLocations(
otherCustomListId,
- listOf(locationCode)
+ listOf(locationId)
)
// Assert
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/RelayListFilterRepositoryTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/RelayListFilterRepositoryTest.kt
new file mode 100644
index 0000000000..c8027240a2
--- /dev/null
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/RelayListFilterRepositoryTest.kt
@@ -0,0 +1,174 @@
+package net.mullvad.mullvadvpn.repository
+
+import app.cash.turbine.test
+import arrow.core.left
+import arrow.core.right
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.every
+import io.mockk.mockk
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.ProviderId
+import net.mullvad.mullvadvpn.lib.model.Providers
+import net.mullvad.mullvadvpn.lib.model.SetWireguardConstraintsError
+import net.mullvad.mullvadvpn.lib.model.Settings
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class RelayListFilterRepositoryTest {
+ private val mockManagementService: ManagementService = mockk()
+
+ private lateinit var relayListFilterRepository: RelayListFilterRepository
+
+ private val settingsFlow = MutableStateFlow(mockk<Settings>(relaxed = true))
+
+ @BeforeEach
+ fun setUp() {
+ every { mockManagementService.settings } returns settingsFlow
+ relayListFilterRepository =
+ RelayListFilterRepository(
+ managementService = mockManagementService,
+ dispatcher = UnconfinedTestDispatcher()
+ )
+ }
+
+ @Test
+ fun `when settings is updated selected ownership should update`() = runTest {
+ // Arrange
+ val mockSettings: Settings = mockk()
+ val selectedOwnership: Constraint<Ownership> = Constraint.Only(Ownership.MullvadOwned)
+ every { mockSettings.relaySettings.relayConstraints.ownership } returns selectedOwnership
+
+ // Act, Assert
+ relayListFilterRepository.selectedOwnership.test {
+ assertEquals(Constraint.Any, awaitItem())
+ settingsFlow.emit(mockSettings)
+ assertEquals(selectedOwnership, awaitItem())
+ }
+ }
+
+ @Test
+ fun `when settings is updated selected providers should update`() = runTest {
+ // Arrange
+ val mockSettings: Settings = mockk()
+ val selectedProviders: Constraint<Providers> =
+ Constraint.Only(Providers(setOf(ProviderId("Prove"))))
+ every { mockSettings.relaySettings.relayConstraints.providers } returns selectedProviders
+
+ // Act, Assert
+ relayListFilterRepository.selectedProviders.test {
+ assertEquals(Constraint.Any, awaitItem())
+ settingsFlow.emit(mockSettings)
+ assertEquals(selectedProviders, awaitItem())
+ }
+ }
+
+ @Test
+ fun `when successfully updating selected ownership and filter should return successful`() =
+ runTest {
+ // Arrange
+ val ownership = Constraint.Any
+ val providers = Constraint.Any
+ coEvery { mockManagementService.setOwnershipAndProviders(ownership, providers) } returns
+ Unit.right()
+
+ // Act
+ val result =
+ relayListFilterRepository.updateSelectedOwnershipAndProviderFilter(
+ ownership,
+ providers
+ )
+
+ // Assert
+ coVerify { mockManagementService.setOwnershipAndProviders(ownership, providers) }
+ assertEquals(Unit.right(), result)
+ }
+
+ @Test
+ fun `when failing to update selected ownership and filter should return SetWireguardConstraintsError`() =
+ runTest {
+ // Arrange
+ val ownership = Constraint.Any
+ val providers = Constraint.Any
+ val error = SetWireguardConstraintsError.Unknown(mockk())
+ coEvery { mockManagementService.setOwnershipAndProviders(ownership, providers) } returns
+ error.left()
+
+ // Act
+ val result =
+ relayListFilterRepository.updateSelectedOwnershipAndProviderFilter(
+ ownership,
+ providers
+ )
+
+ // Assert
+ coVerify { mockManagementService.setOwnershipAndProviders(ownership, providers) }
+ assertEquals(error.left(), result)
+ }
+
+ @Test
+ fun `when successfully updating selected ownership should return successful`() = runTest {
+ // Arrange
+ val ownership = Constraint.Only(Ownership.Rented)
+ coEvery { mockManagementService.setOwnership(ownership) } returns Unit.right()
+
+ // Act
+ val result = relayListFilterRepository.updateSelectedOwnership(ownership)
+
+ // Assert
+ coVerify { mockManagementService.setOwnership(ownership) }
+ assertEquals(Unit.right(), result)
+ }
+
+ @Test
+ fun `when failing to update selected ownership should return SetWireguardConstraintsError`() =
+ runTest {
+ // Arrange
+ val ownership = Constraint.Only(Ownership.Rented)
+ val error = SetWireguardConstraintsError.Unknown(mockk())
+ coEvery { mockManagementService.setOwnership(ownership) } returns error.left()
+
+ // Act
+ val result = relayListFilterRepository.updateSelectedOwnership(ownership)
+
+ // Assert
+ coVerify { mockManagementService.setOwnership(ownership) }
+ assertEquals(error.left(), result)
+ }
+
+ @Test
+ fun `when successfully updating selected providers should return successful`() = runTest {
+ // Arrange
+ val providers = Constraint.Only(Providers(setOf(ProviderId("Mopp"))))
+ coEvery { mockManagementService.setProviders(providers) } returns Unit.right()
+
+ // Act
+ val result = relayListFilterRepository.updateSelectedProviders(providers)
+
+ // Assert
+ coVerify { mockManagementService.setProviders(providers) }
+ assertEquals(Unit.right(), result)
+ }
+
+ @Test
+ fun `when failing to update selected providers should return SetWireguardConstraintsError`() =
+ runTest {
+ // Arrange
+ val providers = Constraint.Only(Providers(setOf(ProviderId("Mopp"))))
+ val error = SetWireguardConstraintsError.Unknown(mockk())
+ coEvery { mockManagementService.setProviders(providers) } returns error.left()
+
+ // Act
+ val result = relayListFilterRepository.updateSelectedProviders(providers)
+
+ // Assert
+ coVerify { mockManagementService.setProviders(providers) }
+ assertEquals(error.left(), result)
+ }
+}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt
deleted file mode 100644
index 8fd21c5533..0000000000
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ConnectionProxyTest.kt
+++ /dev/null
@@ -1,82 +0,0 @@
-package net.mullvad.mullvadvpn.ui.serviceconnection
-
-import android.os.DeadObjectException
-import android.os.Looper
-import android.os.Messenger
-import android.util.Log
-import io.mockk.MockKAnnotations
-import io.mockk.Runs
-import io.mockk.every
-import io.mockk.impl.annotations.MockK
-import io.mockk.just
-import io.mockk.mockk
-import io.mockk.mockkObject
-import io.mockk.mockkStatic
-import io.mockk.slot
-import io.mockk.unmockkAll
-import kotlin.reflect.KClass
-import kotlin.test.assertEquals
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-
-class ConnectionProxyTest {
-
- @MockK private lateinit var mockedMainLooper: Looper
-
- @MockK private lateinit var connection: Messenger
-
- @MockK private lateinit var mockedDispatchingHandler: EventDispatcher
- lateinit var connectionProxy: ConnectionProxy
-
- @BeforeEach
- fun setup() {
- mockkStatic(Looper::class)
- mockkStatic(Log::class)
- MockKAnnotations.init(this)
- mockkObject(Request.Connect, Request.Disconnect)
- every { Request.Connect.message } returns mockk()
- every { Request.Disconnect.message } returns mockk()
- every { Looper.getMainLooper() } returns mockedMainLooper
- every { Log.e(any(), any()) } returns mockk(relaxed = true)
- }
-
- @AfterEach
- fun tearDown() {
- unmockkAll()
- }
-
- @Test
- fun `initialize connection proxy should work`() {
- // Arrange
- val eventType = slot<KClass<Event.TunnelStateChange>>()
- every { mockedDispatchingHandler.registerHandler(capture(eventType), any()) } just Runs
- // Create ConnectionProxy instance and assert initial Event type
- connectionProxy = ConnectionProxy(connection, mockedDispatchingHandler)
- assertEquals(Event.TunnelStateChange::class, eventType.captured.java.kotlin)
- }
-
- @Test
- fun `normal connect and disconnect should not crash`() {
- // Arrange
- every { connection.send(any()) } just Runs
- every { mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any()) } just Runs
- // Act and Assert no crashes
- connectionProxy = ConnectionProxy(connection, mockedDispatchingHandler)
- connectionProxy.connect()
- connectionProxy.disconnect()
- }
-
- @Test
- fun `connect should catch DeadObjectException`() {
- // Arrange
- every { connection.send(any()) } throws DeadObjectException()
- every { mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any()) } just Runs
- // Act and Assert no crashes
- connectionProxy = ConnectionProxy(connection, mockedDispatchingHandler)
- connectionProxy.connect()
- }
-}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt
deleted file mode 100644
index 81b518199c..0000000000
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/ServiceConnectionDeviceDataSourceTest.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package net.mullvad.mullvadvpn.ui.serviceconnection
-
-import android.os.DeadObjectException
-import android.os.Looper
-import android.os.Messenger
-import io.mockk.MockKAnnotations
-import io.mockk.Runs
-import io.mockk.every
-import io.mockk.impl.annotations.MockK
-import io.mockk.just
-import io.mockk.mockk
-import io.mockk.mockkObject
-import io.mockk.mockkStatic
-import io.mockk.unmockkAll
-import kotlin.reflect.KClass
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.EventDispatcher
-import net.mullvad.mullvadvpn.lib.ipc.Request
-import org.junit.jupiter.api.AfterEach
-import org.junit.jupiter.api.BeforeEach
-import org.junit.jupiter.api.Test
-
-class ServiceConnectionDeviceDataSourceTest {
- @MockK private lateinit var mockedMainLooper: Looper
-
- @MockK private lateinit var mockedDispatchingHandler: EventDispatcher
-
- @MockK private lateinit var connection: Messenger
-
- lateinit var serviceConnectionDeviceDataSource: ServiceConnectionDeviceDataSource
-
- @BeforeEach
- fun setup() {
- mockkStatic(Looper::class)
- mockkStatic(android.util.Log::class)
- MockKAnnotations.init(this)
- mockkObject(Request.GetDevice, Request.RefreshDeviceState)
- every { Request.GetDevice.message } returns mockk()
- every { Request.RefreshDeviceState.message } returns mockk()
- every { Looper.getMainLooper() } returns mockedMainLooper
- every { android.util.Log.e(any(), any()) } returns mockk(relaxed = true)
- }
-
- @AfterEach
- fun tearDown() {
- unmockkAll()
- }
-
- @Test
- fun `get device should work`() {
- // Arrange
- every { connection.send(any()) } just Runs
- every { mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any()) } just Runs
- // Act and Assert no crashes
- serviceConnectionDeviceDataSource =
- ServiceConnectionDeviceDataSource(connection, mockedDispatchingHandler)
- serviceConnectionDeviceDataSource.getDevice()
- }
-
- @Test
- fun `get device should catch DeadObjectException`() {
- // Arrange
- every { connection.send(any()) } throws DeadObjectException()
- every { mockedDispatchingHandler.registerHandler(any<KClass<Event>>(), any()) } just Runs
- // Act and Assert no crashes
- serviceConnectionDeviceDataSource =
- ServiceConnectionDeviceDataSource(connection, mockedDispatchingHandler)
- serviceConnectionDeviceDataSource.getDevice()
- }
-}
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
index 39bfae63d8..11d574b663 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/AccountExpiryNotificationUseCaseTest.kt
@@ -10,8 +10,8 @@ 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.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.repository.InAppNotification
import org.joda.time.DateTime
import org.junit.jupiter.api.AfterEach
@@ -22,7 +22,7 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class AccountExpiryNotificationUseCaseTest {
- private val accountExpiry = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing)
+ private val accountExpiry = MutableStateFlow<AccountData?>(null)
private lateinit var accountExpiryNotificationUseCase: AccountExpiryNotificationUseCase
@BeforeEach
@@ -30,7 +30,7 @@ class AccountExpiryNotificationUseCaseTest {
MockKAnnotations.init(this)
val accountRepository = mockk<AccountRepository>()
- every { accountRepository.accountExpiryState } returns accountExpiry
+ every { accountRepository.accountData } returns accountExpiry
accountExpiryNotificationUseCase = AccountExpiryNotificationUseCase(accountRepository)
}
@@ -53,11 +53,11 @@ class AccountExpiryNotificationUseCaseTest {
// Arrange, Act, Assert
accountExpiryNotificationUseCase.notifications().test {
assertTrue { awaitItem().isEmpty() }
- val closeToExpiry = AccountExpiry.Available(DateTime.now().plusDays(2))
+ val closeToExpiry = AccountData(mockk(relaxed = true), DateTime.now().plusDays(2))
accountExpiry.value = closeToExpiry
assertEquals(
- listOf(InAppNotification.AccountExpiry(closeToExpiry.expiryDateTime)),
+ listOf(InAppNotification.AccountExpiry(closeToExpiry.expiryDate)),
awaitItem()
)
}
@@ -68,7 +68,7 @@ class AccountExpiryNotificationUseCaseTest {
// Arrange, Act, Assert
accountExpiryNotificationUseCase.notifications().test {
assertTrue { awaitItem().isEmpty() }
- accountExpiry.value = AccountExpiry.Available(DateTime.now().plusDays(4))
+ accountExpiry.value = AccountData(mockk(relaxed = true), DateTime.now().plusDays(4))
expectNoEvents()
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt
index 4dfb95768b..bb19d42d13 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/CustomListActionUseCaseTest.kt
@@ -1,78 +1,80 @@
package net.mullvad.mullvadvpn.usecase
+import arrow.core.left
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
-import kotlin.test.assertIs
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.communication.Created
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
-import net.mullvad.mullvadvpn.compose.communication.CustomListResult
-import net.mullvad.mullvadvpn.model.CreateCustomListResult
-import net.mullvad.mullvadvpn.model.CustomList
-import net.mullvad.mullvadvpn.model.CustomListName
-import net.mullvad.mullvadvpn.model.CustomListsError
-import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
-import net.mullvad.mullvadvpn.model.UpdateCustomListResult
-import net.mullvad.mullvadvpn.relaylist.RelayItem
-import net.mullvad.mullvadvpn.relaylist.getRelayItemsByCodes
+import net.mullvad.mullvadvpn.compose.communication.Deleted
+import net.mullvad.mullvadvpn.compose.communication.LocationsChanged
+import net.mullvad.mullvadvpn.compose.communication.Renamed
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListAlreadyExists
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.NameAlreadyExists
+import net.mullvad.mullvadvpn.lib.model.RelayItem
import net.mullvad.mullvadvpn.repository.CustomListsRepository
+import net.mullvad.mullvadvpn.repository.RelayListRepository
+import net.mullvad.mullvadvpn.usecase.customlists.CreateWithLocationsError
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
-import net.mullvad.mullvadvpn.usecase.customlists.CustomListsException
+import net.mullvad.mullvadvpn.usecase.customlists.RenameError
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
class CustomListActionUseCaseTest {
private val mockCustomListsRepository: CustomListsRepository = mockk()
- private val mockRelayListUseCase: RelayListUseCase = mockk()
+ private val mockRelayListRepository: RelayListRepository = mockk()
private val customListActionUseCase =
CustomListActionUseCase(
customListsRepository = mockCustomListsRepository,
- relayListUseCase = mockRelayListUseCase
+ relayListRepository = mockRelayListRepository
)
+ private val relayListFlow = MutableStateFlow(emptyList<RelayItem.Location.Country>())
+
@BeforeEach
fun setup() {
mockkStatic(RELAY_LIST_EXTENSIONS)
+ every { mockRelayListRepository.relayList } returns relayListFlow
}
@Test
fun `create action should return success when ok`() = runTest {
// Arrange
val name = CustomListName.fromString("test")
- val locationCode = "AB"
+ val locationId = GeoLocationId.Country("se")
val locationName = "Acklaba"
- val createdId = "1"
- val action = CustomListAction.Create(name = name, locations = listOf(locationCode))
+ val createdId = CustomListId("1")
+ val action = CustomListAction.Create(name = name, locations = listOf(locationId))
val expectedResult =
- Result.success(
- CustomListResult.Created(
+ Created(
id = createdId,
name = name,
- locationName = locationName,
+ locationNames = listOf(locationName),
undo = action.not(createdId)
)
- )
- val relayItem =
- RelayItem.Country(
- name = locationName,
- code = locationCode,
- expanded = false,
- cities = emptyList()
- )
- val mockLocations: List<RelayItem.Country> = listOf(relayItem)
- coEvery { mockCustomListsRepository.createCustomList(name) } returns
- CreateCustomListResult.Ok(createdId)
+ .right()
+ coEvery { mockCustomListsRepository.createCustomList(name) } returns createdId.right()
coEvery {
- mockCustomListsRepository.updateCustomListLocationsFromCodes(
- createdId,
- listOf(locationCode)
+ mockCustomListsRepository.updateCustomListLocations(createdId, listOf(locationId))
+ } returns Unit.right()
+ relayListFlow.value =
+ listOf(
+ RelayItem.Location.Country(
+ id = locationId,
+ name = locationName,
+ expanded = false,
+ cities = emptyList()
+ )
)
- } returns UpdateCustomListResult.Ok
- coEvery { mockRelayListUseCase.fullRelayList() } returns flowOf(mockLocations)
- every { mockLocations.getRelayItemsByCodes(listOf(locationCode)) } returns mockLocations
// Act
val result = customListActionUseCase.performAction(action)
@@ -85,20 +87,17 @@ class CustomListActionUseCaseTest {
fun `create action should return error when name already exists`() = runTest {
// Arrange
val name = CustomListName.fromString("test")
- val locationCode = "AB"
- val action = CustomListAction.Create(name = name, locations = listOf(locationCode))
- val expectedError = CustomListsError.CustomListExists
+ val locationId = GeoLocationId.Country("AB")
+ val action = CustomListAction.Create(name = name, locations = listOf(locationId))
+ val expectedError = CreateWithLocationsError.Create(CustomListAlreadyExists).left()
coEvery { mockCustomListsRepository.createCustomList(name) } returns
- CreateCustomListResult.Error(CustomListsError.CustomListExists)
+ CustomListAlreadyExists.left()
// Act
val result = customListActionUseCase.performAction(action)
// Assert
- assertIs<Result<CustomListsException>>(result)
- val exception = result.exceptionOrNull()
- assertIs<CustomListsException>(exception)
- assertEquals(expectedError, exception.error)
+ assertEquals(expectedError, result)
}
@Test
@@ -106,13 +105,12 @@ class CustomListActionUseCaseTest {
// Arrange
val name = CustomListName.fromString("test")
val newName = CustomListName.fromString("test2")
- val customListId = "1"
- val action =
- CustomListAction.Rename(customListId = customListId, name = name, newName = newName)
- val expectedResult = Result.success(CustomListResult.Renamed(undo = action.not()))
+ val customListId = CustomListId("1")
+ val action = CustomListAction.Rename(id = customListId, name = name, newName = newName)
+ val expectedResult = Renamed(undo = action.not()).right()
coEvery {
mockCustomListsRepository.updateCustomListName(id = customListId, name = newName)
- } returns UpdateCustomListResult.Ok
+ } returns Unit.right()
// Act
val result = customListActionUseCase.performAction(action)
@@ -126,45 +124,38 @@ class CustomListActionUseCaseTest {
// Arrange
val name = CustomListName.fromString("test")
val newName = CustomListName.fromString("test2")
- val customListId = "1"
- val action =
- CustomListAction.Rename(customListId = customListId, name = name, newName = newName)
- val expectedError = CustomListsError.CustomListExists
+ val customListId = CustomListId("1")
+ val action = CustomListAction.Rename(id = customListId, name = name, newName = newName)
coEvery {
mockCustomListsRepository.updateCustomListName(id = customListId, name = newName)
- } returns UpdateCustomListResult.Error(expectedError)
+ } returns NameAlreadyExists(newName.value).left()
+
+ val expectedError = RenameError(NameAlreadyExists(newName.value)).left()
// Act
val result = customListActionUseCase.performAction(action)
// Assert
- assertIs<Result<CustomListsException>>(result)
- val exception = result.exceptionOrNull()
- assertIs<CustomListsException>(exception)
- assertEquals(expectedError, exception.error)
+ assertEquals(expectedError, result)
}
@Test
fun `delete action should return successful with deleted list`() = runTest {
// Arrange
- val mockCustomList: CustomList = mockk()
- val mockLocation: GeographicLocationConstraint.Country = mockk()
- val mockLocations: ArrayList<GeographicLocationConstraint> = arrayListOf(mockLocation)
+ val mockLocation: GeoLocationId.Country = mockk()
+ val mockLocations: List<GeoLocationId> = listOf(mockLocation)
val name = CustomListName.fromString("test")
- val customListId = "1"
- val locationCode = "AB"
- val action = CustomListAction.Delete(customListId = customListId)
+ val customListId = CustomListId("1")
+ val mockCustomList = CustomList(id = customListId, name = name, locations = mockLocations)
+ val location = GeoLocationId.Country("AB")
+ val action = CustomListAction.Delete(id = customListId)
val expectedResult =
- Result.success(
- CustomListResult.Deleted(
- undo = action.not(name = name, locations = listOf(locationCode))
- )
- )
- every { mockCustomList.locations } returns mockLocations
- every { mockCustomList.name } returns name.value
- every { mockLocation.countryCode } returns locationCode
- coEvery { mockCustomListsRepository.deleteCustomList(id = customListId) } returns true
- every { mockCustomListsRepository.getCustomListById(customListId) } returns mockCustomList
+ Deleted(undo = action.not(name = name, locations = listOf(location))).right()
+ every { mockLocation.countryCode } returns location.countryCode
+ coEvery { mockCustomListsRepository.deleteCustomList(id = customListId) } returns
+ Unit.right()
+ coEvery { mockCustomListsRepository.getCustomListById(customListId) } returns
+ mockCustomList.right()
// Act
val result = customListActionUseCase.performAction(action)
@@ -177,35 +168,20 @@ class CustomListActionUseCaseTest {
fun `update locations action should return success with changed locations`() = runTest {
// Arrange
val name = CustomListName.fromString("test")
- val oldLocationCodes = listOf("AB", "CD")
- val newLocationCodes = listOf("EF", "GH")
- val oldLocations: ArrayList<GeographicLocationConstraint> =
- arrayListOf(
- GeographicLocationConstraint.Country("AB"),
- GeographicLocationConstraint.Country("CD")
- )
- val customListId = "1"
- val customList = CustomList(id = customListId, name = name.value, locations = oldLocations)
- val action =
- CustomListAction.UpdateLocations(
- customListId = customListId,
- locations = newLocationCodes
- )
+ val newLocations = listOf(GeoLocationId.Country("EF"), GeoLocationId.Country("GH"))
+ val oldLocations: ArrayList<GeoLocationId> =
+ arrayListOf(GeoLocationId.Country("AB"), GeoLocationId.Country("CD"))
+ val customListId = CustomListId("1")
+ val customList = CustomList(id = customListId, name = name, locations = oldLocations)
+ val action = CustomListAction.UpdateLocations(id = customListId, locations = newLocations)
val expectedResult =
- Result.success(
- CustomListResult.LocationsChanged(
- name = name,
- undo = action.not(locations = oldLocationCodes)
- )
- )
- coEvery { mockCustomListsRepository.getCustomListById(customListId) } returns customList
+ LocationsChanged(name = name, undo = action.not(locations = oldLocations)).right()
+ coEvery { mockCustomListsRepository.getCustomListById(customListId) } returns
+ customList.right()
coEvery {
- mockCustomListsRepository.updateCustomListLocationsFromCodes(
- customListId,
- newLocationCodes
- )
- } returns UpdateCustomListResult.Ok
+ mockCustomListsRepository.updateCustomListLocations(customListId, newLocations)
+ } returns Unit.right()
// Act
val result = customListActionUseCase.performAction(action)
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
index 691bb99131..b55da83f51 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt
@@ -10,11 +10,13 @@ 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.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.Device
+import net.mullvad.mullvadvpn.lib.model.DeviceId
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.repository.InAppNotification
+import org.joda.time.DateTime
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -25,9 +27,14 @@ class NewDeviceUseNotificationCaseTest {
private val deviceName = "Frank Zebra"
private val deviceState =
- MutableStateFlow<DeviceState>(
+ MutableStateFlow<DeviceState?>(
DeviceState.LoggedIn(
- accountAndDevice = AccountAndDevice("", Device("", deviceName, byteArrayOf(), ""))
+ AccountToken("1234123412341234"),
+ Device(
+ id = DeviceId.fromString(UUID),
+ name = deviceName,
+ creationDate = DateTime.now()
+ )
)
)
private lateinit var newDeviceNotificationUseCase: NewDeviceNotificationUseCase
@@ -79,4 +86,8 @@ class NewDeviceUseNotificationCaseTest {
assertEquals(awaitItem(), emptyList())
}
}
+
+ companion object {
+ private const val UUID = "12345678-1234-5678-1234-567812345678"
+ }
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt
index 326e183445..088c9a435c 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt
@@ -9,21 +9,19 @@ import kotlin.time.Duration.Companion.days
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.receiveAsFlow
+import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.lib.ipc.MessageHandler
-import net.mullvad.mullvadvpn.lib.ipc.events
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.tunnel.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.ErrorStateCause
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import org.joda.time.DateTime
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@@ -31,10 +29,10 @@ import org.junit.jupiter.api.Test
class OutOfTimeUseCaseTest {
private val mockAccountRepository: AccountRepository = mockk()
- private val mockMessageHandler: MessageHandler = mockk()
+ private val mockConnectionProxy: ConnectionProxy = mockk()
- private lateinit var events: Channel<Event.TunnelStateChange>
- private lateinit var expiry: MutableStateFlow<AccountExpiry>
+ private lateinit var events: Channel<TunnelState>
+ private lateinit var expiry: MutableStateFlow<AccountData?>
private val dispatcher = StandardTestDispatcher()
private val scope = TestScope(dispatcher)
@@ -44,15 +42,14 @@ class OutOfTimeUseCaseTest {
@BeforeEach
fun setup() {
events = Channel()
- expiry = MutableStateFlow(AccountExpiry.Missing)
- every { mockAccountRepository.accountExpiryState } returns expiry
- every { mockMessageHandler.events<Event.TunnelStateChange>() } returns
- events.receiveAsFlow()
+ expiry = MutableStateFlow(null)
+ every { mockAccountRepository.accountData } returns expiry
+ every { mockConnectionProxy.tunnelState } returns events.consumeAsFlow()
Dispatchers.setMain(dispatcher)
outOfTimeUseCase =
- OutOfTimeUseCase(mockAccountRepository, mockMessageHandler, scope.backgroundScope)
+ OutOfTimeUseCase(mockConnectionProxy, mockAccountRepository, scope.backgroundScope)
}
@AfterEach
@@ -73,14 +70,13 @@ class OutOfTimeUseCaseTest {
fun `tunnel is blocking because out of time should emit true`() =
scope.runTest {
// Arrange
- // Act, Assert
val errorStateCause = ErrorStateCause.AuthFailed("[EXPIRED_ACCOUNT]")
val tunnelStateError = TunnelState.Error(ErrorState(errorStateCause, true))
- val errorChange = Event.TunnelStateChange(tunnelStateError)
+ // Act, Assert
outOfTimeUseCase.isOutOfTime.test {
assertEquals(null, awaitItem())
- events.send(errorChange)
+ events.send(tunnelStateError)
assertEquals(true, awaitItem())
}
}
@@ -89,16 +85,16 @@ class OutOfTimeUseCaseTest {
fun `tunnel is connected should emit false`() =
scope.runTest {
// Arrange
- val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1))
+ val expiredAccountExpiry =
+ AccountData(mockk(relaxed = true), DateTime.now().plusDays(1))
val tunnelStateChanges =
listOf(
- TunnelState.Disconnected(),
- TunnelState.Connected(mockk(), null),
- TunnelState.Connecting(null, null),
- TunnelState.Disconnecting(mockk()),
- TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, false)),
- )
- .map(Event::TunnelStateChange)
+ TunnelState.Disconnected(),
+ TunnelState.Connected(mockk(), null),
+ TunnelState.Connecting(null, null),
+ TunnelState.Disconnecting(mockk()),
+ TunnelState.Error(ErrorState(ErrorStateCause.StartTunnelError, false)),
+ )
// Act, Assert
outOfTimeUseCase.isOutOfTime.test {
@@ -118,7 +114,8 @@ class OutOfTimeUseCaseTest {
fun `account expiry that has expired should emit true`() =
scope.runTest {
// Arrange
- val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().minusDays(1))
+ val expiredAccountExpiry =
+ AccountData(mockk(relaxed = true), DateTime.now().minusDays(1))
// Act, Assert
outOfTimeUseCase.isOutOfTime.test {
assertEquals(null, awaitItem())
@@ -131,7 +128,8 @@ class OutOfTimeUseCaseTest {
fun `account expiry that has not expired should emit false`() =
scope.runTest {
// Arrange
- val notExpiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusDays(1))
+ val notExpiredAccountExpiry =
+ AccountData(mockk(relaxed = true), DateTime.now().plusDays(1))
// Act, Assert
outOfTimeUseCase.isOutOfTime.test {
@@ -145,7 +143,8 @@ class OutOfTimeUseCaseTest {
fun `account that expires without new expiry event should emit true`() =
runTest(dispatcher) {
// Arrange
- val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(100))
+ val expiredAccountExpiry =
+ AccountData(mockk(relaxed = true), DateTime.now().plusSeconds(100))
// Act, Assert
outOfTimeUseCase.isOutOfTime.test {
// Initial event
@@ -167,9 +166,10 @@ class OutOfTimeUseCaseTest {
@Test
fun `account that is about to expire but is refilled should emit false`() = runTest {
// Arrange
- val initialAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(100))
+ val initialAccountExpiry =
+ AccountData(mockk(relaxed = true), DateTime.now().plusSeconds(100))
val updatedExpiry =
- AccountExpiry.Available(initialAccountExpiry.expiryDateTime.plusDays(30))
+ AccountData(mockk(relaxed = true), initialAccountExpiry.expiryDate.plusDays(30))
// Act, Assert
outOfTimeUseCase.isOutOfTime.test {
@@ -196,9 +196,10 @@ class OutOfTimeUseCaseTest {
@Test
fun `expired account that is refilled should emit false`() = runTest {
// Arrange
- val initialAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(100))
+ val initialAccountExpiry =
+ AccountData(mockk(relaxed = true), DateTime.now().plusSeconds(100))
val updatedExpiry =
- AccountExpiry.Available(initialAccountExpiry.expiryDateTime.plusDays(30))
+ AccountData(mockk(relaxed = true), initialAccountExpiry.expiryDate.plusDays(30))
// Act, Assert
outOfTimeUseCase.isOutOfTime.test {
// Initial event
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
index 82126099d8..a2e8db36fd 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/TunnelStateNotificationUseCaseTest.kt
@@ -10,15 +10,11 @@ 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.lib.model.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
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.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -27,26 +23,19 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class TunnelStateNotificationUseCaseTest {
- 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())
+ private val tunnelState = MutableStateFlow<TunnelState>(TunnelState.Disconnected())
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
- every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState
-
- every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
- every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
+ every { mockConnectionProxy.tunnelState } returns tunnelState
tunnelStateNotificationUseCase =
- TunnelStateNotificationUseCase(serviceConnectionManager = mockServiceConnectionManager)
+ TunnelStateNotificationUseCase(connectionProxy = mockConnectionProxy)
}
@AfterEach
@@ -65,10 +54,8 @@ class TunnelStateNotificationUseCaseTest {
tunnelStateNotificationUseCase.notifications().test {
// Arrange, Act
assertEquals(emptyList(), awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
val errorState: ErrorState = mockk()
- eventNotifierTunnelUiState.notify(TunnelState.Error(errorState))
+ tunnelState.emit(TunnelState.Error(errorState))
// Assert
assertEquals(listOf(InAppNotification.TunnelStateError(errorState)), awaitItem())
@@ -81,11 +68,7 @@ class TunnelStateNotificationUseCaseTest {
tunnelStateNotificationUseCase.notifications().test {
// Arrange, Act
assertEquals(emptyList(), awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- eventNotifierTunnelUiState.notify(
- TunnelState.Disconnecting(ActionAfterDisconnect.Block)
- )
+ tunnelState.emit(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
index fbc677b461..1630ed757f 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/VersionNotificationUseCaseTest.kt
@@ -4,7 +4,6 @@ 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
@@ -13,11 +12,7 @@ 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 net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -26,39 +21,22 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class VersionNotificationUseCaseTest {
- private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
- private lateinit var mockAppVersionInfoCache: AppVersionInfoCache
- private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+ private val mockAppVersionInfoRepository: AppVersionInfoRepository = mockk()
- private val serviceConnectionState =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
private val versionInfo =
MutableStateFlow(
- VersionInfo(
- currentVersion = null,
- upgradeVersion = null,
- isOutdated = false,
- isSupported = true
- )
+ VersionInfo(currentVersion = "", isSupported = true, suggestedUpgradeVersion = null)
)
private lateinit var versionNotificationUseCase: VersionNotificationUseCase
@BeforeEach
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 {}
+ every { mockAppVersionInfoRepository.versionInfo() } returns versionInfo
versionNotificationUseCase =
VersionNotificationUseCase(
- serviceConnectionManager = mockServiceConnectionManager,
+ appVersionInfoRepository = mockAppVersionInfoRepository,
isVersionInfoNotificationEnabled = true
)
}
@@ -80,9 +58,11 @@ class VersionNotificationUseCaseTest {
versionNotificationUseCase.notifications().test {
// Arrange, Act
val upgradeVersionInfo =
- VersionInfo("1.0", "1.1", isOutdated = true, isSupported = true)
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ VersionInfo(
+ currentVersion = "1.0",
+ isSupported = true,
+ suggestedUpgradeVersion = "1.1"
+ )
awaitItem()
versionInfo.value = upgradeVersionInfo
@@ -100,9 +80,11 @@ class VersionNotificationUseCaseTest {
versionNotificationUseCase.notifications().test {
// Arrange, Act
val upgradeVersionInfo =
- VersionInfo("1.0", "", isOutdated = false, isSupported = false)
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ VersionInfo(
+ currentVersion = "1.0",
+ isSupported = false,
+ suggestedUpgradeVersion = null
+ )
awaitItem()
versionInfo.value = upgradeVersionInfo
@@ -113,8 +95,4 @@ class VersionNotificationUseCaseTest {
)
}
}
-
- 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/AccountViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt
index 61758c2d1d..362fc457f5 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt
@@ -8,7 +8,6 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
-import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.flow.MutableStateFlow
@@ -16,20 +15,18 @@ import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.PaymentState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.common.test.assertLists
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.Device
+import net.mullvad.mullvadvpn.lib.model.DeviceId
+import net.mullvad.mullvadvpn.lib.model.DeviceState
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
import net.mullvad.mullvadvpn.lib.payment.model.ProductId
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
-import net.mullvad.mullvadvpn.model.AccountAndDevice
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.Device
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.mullvadvpn.repository.DeviceRepository
-import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
-import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
+import org.joda.time.DateTime
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -39,43 +36,35 @@ import org.junit.jupiter.api.extension.ExtendWith
class AccountViewModelTest {
private val mockAccountRepository: AccountRepository = mockk(relaxUnitFun = true)
- private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
- private val mockDeviceRepository: DeviceRepository = mockk()
- private val mockAuthTokenCache: AuthTokenCache = mockk()
+ private val mockDeviceRepository: DeviceRepository = mockk(relaxUnitFun = true)
private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true)
- private val deviceState: MutableStateFlow<DeviceState> = MutableStateFlow(DeviceState.Initial)
+ private val deviceState: MutableStateFlow<DeviceState?> = MutableStateFlow(null)
private val paymentAvailability = MutableStateFlow<PaymentAvailability?>(null)
private val purchaseResult = MutableStateFlow<PurchaseResult?>(null)
- private val accountExpiryState = MutableStateFlow(AccountExpiry.Missing)
+ private val accountExpiryState = MutableStateFlow(null)
- private val dummyAccountAndDevice: AccountAndDevice =
- AccountAndDevice(
+ private val dummyDevice =
+ Device(id = DeviceId.fromString(UUID), name = "fake_name", creationDate = DateTime.now())
+ private val dummyAccountToken: AccountToken =
+ AccountToken(
DUMMY_DEVICE_NAME,
- Device(
- id = "fake_id",
- name = "fake_name",
- pubkey = byteArrayOf(),
- created = "mock_date"
- )
)
private lateinit var viewModel: AccountViewModel
@BeforeEach
fun setup() {
- mockkStatic(CACHE_EXTENSION_CLASS)
mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS)
- every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache
+ every { mockAccountRepository.accountData } returns accountExpiryState
every { mockDeviceRepository.deviceState } returns deviceState
- every { mockAccountRepository.accountExpiryState } returns accountExpiryState
coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResult
coEvery { mockPaymentUseCase.paymentAvailability } returns paymentAvailability
+ coEvery { mockAccountRepository.getAccountData() } returns null
viewModel =
AccountViewModel(
accountRepository = mockAccountRepository,
- serviceConnectionManager = mockServiceConnectionManager,
deviceRepository = mockDeviceRepository,
paymentUseCase = mockPaymentUseCase,
isPlayBuild = false
@@ -92,7 +81,8 @@ class AccountViewModelTest {
// Act, Assert
viewModel.uiState.test {
awaitItem() // Default state
- deviceState.value = DeviceState.LoggedIn(accountAndDevice = dummyAccountAndDevice)
+ deviceState.value =
+ DeviceState.LoggedIn(accountToken = dummyAccountToken, device = dummyDevice)
val result = awaitItem()
assertEquals(DUMMY_DEVICE_NAME, result.accountNumber)
}
@@ -104,7 +94,7 @@ class AccountViewModelTest {
viewModel.onLogoutClick()
// Assert
- verify { mockAccountRepository.logout() }
+ coVerify { mockAccountRepository.logout() }
}
@Test
@@ -184,7 +174,7 @@ class AccountViewModelTest {
viewModel.onClosePurchaseResultDialog(success = true)
// Assert
- verify { mockAccountRepository.fetchAccountExpiry() }
+ coVerify { mockAccountRepository.getAccountData() }
}
@Test
@@ -221,9 +211,9 @@ class AccountViewModelTest {
}
companion object {
- private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
private const val PURCHASE_RESULT_EXTENSIONS_CLASS =
"net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt"
private const val DUMMY_DEVICE_NAME = "fake_name"
+ private const val UUID = "12345678-1234-5678-1234-567812345678"
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt
index 46126f5ad8..7888f02a4d 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ChangelogViewModelTest.kt
@@ -6,12 +6,11 @@ import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlin.test.assertEquals
import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.BuildConfig
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.lib.model.BuildVersion
import net.mullvad.mullvadvpn.repository.ChangelogRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@@ -25,10 +24,11 @@ class ChangelogViewModelTest {
private lateinit var viewModel: ChangelogViewModel
+ private val buildVersion = BuildVersion("1.0", 10)
+
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
- mockkStatic(EVENT_NOTIFIER_EXTENSION_CLASS)
every { mockedChangelogRepository.setVersionCodeOfMostRecentChangelogShowed(any()) } just
Runs
}
@@ -42,8 +42,8 @@ class ChangelogViewModelTest {
fun `given up to date version code uiSideEffect should not emit`() = runTest {
// Arrange
every { mockedChangelogRepository.getVersionCodeOfMostRecentChangelogShowed() } returns
- buildVersionCode
- viewModel = ChangelogViewModel(mockedChangelogRepository, buildVersionCode, false)
+ buildVersion.code
+ viewModel = ChangelogViewModel(mockedChangelogRepository, buildVersion, false)
// If we have the most up to date version code, we should not show the changelog dialog
viewModel.uiSideEffect.test { expectNoEvents() }
@@ -58,13 +58,10 @@ class ChangelogViewModelTest {
version
every { mockedChangelogRepository.getLastVersionChanges() } returns changes
- viewModel = ChangelogViewModel(mockedChangelogRepository, buildVersionCode, false)
+ viewModel = ChangelogViewModel(mockedChangelogRepository, buildVersion, false)
// Given a new version with a change log we should return it
viewModel.uiSideEffect.test {
- assertEquals(
- awaitItem(),
- Changelog(version = BuildConfig.VERSION_NAME, changes = changes)
- )
+ assertEquals(awaitItem(), Changelog(version = buildVersion.name, changes = changes))
}
}
@@ -74,14 +71,8 @@ class ChangelogViewModelTest {
every { mockedChangelogRepository.getVersionCodeOfMostRecentChangelogShowed() } returns -1
every { mockedChangelogRepository.getLastVersionChanges() } returns emptyList()
- viewModel = ChangelogViewModel(mockedChangelogRepository, buildVersionCode, false)
+ viewModel = ChangelogViewModel(mockedChangelogRepository, buildVersion, false)
// Given a new version with a change log we should not return it
viewModel.uiSideEffect.test { expectNoEvents() }
}
-
- companion object {
- private const val EVENT_NOTIFIER_EXTENSION_CLASS =
- "net.mullvad.talpid.util.EventNotifierExtensionsKt"
- private const val buildVersionCode = 10
- }
}
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 7e207a15a4..2de7724c69 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
@@ -2,12 +2,13 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.right
import io.mockk.coEvery
+import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
-import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertNull
@@ -15,33 +16,31 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.ConnectUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.GeoIpLocation
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.relaylist.RelayItem
-import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.ErrorState
+import net.mullvad.mullvadvpn.lib.model.GeoIpLocation
+import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
+import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
+import net.mullvad.mullvadvpn.lib.shared.VpnPermissionRepository
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
-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.mullvadvpn.ui.serviceconnection.authTokenCache
-import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
+import net.mullvad.mullvadvpn.usecase.LastKnownLocationUseCase
import net.mullvad.mullvadvpn.usecase.OutOfTimeUseCase
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
-import net.mullvad.mullvadvpn.usecase.RelayListUseCase
-import net.mullvad.mullvadvpn.util.appVersionCallbackFlow
-import net.mullvad.talpid.tunnel.ErrorState
-import net.mullvad.talpid.util.EventNotifier
+import net.mullvad.mullvadvpn.usecase.SelectedLocationTitleUseCase
+import net.mullvad.mullvadvpn.util.toInAddress
+import net.mullvad.mullvadvpn.util.toOutAddress
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -54,23 +53,12 @@ class ConnectViewModelTest {
private lateinit var viewModel: ConnectViewModel
private val serviceConnectionState =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
- private val versionInfo =
- MutableStateFlow(
- VersionInfo(
- currentVersion = null,
- upgradeVersion = null,
- isOutdated = false,
- isSupported = true
- )
- )
- private val accountExpiryState = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing)
- private val deviceState = MutableStateFlow<DeviceState>(DeviceState.Initial)
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Unbound)
+ private val accountExpiryState = MutableStateFlow<AccountData?>(null)
+ private val device = MutableStateFlow<DeviceState?>(null)
private val notifications = MutableStateFlow<List<InAppNotification>>(emptyList())
// Service connections
- private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
- private lateinit var mockAppVersionInfoCache: AppVersionInfoCache
private val mockConnectionProxy: ConnectionProxy = mockk()
private val mockLocation: GeoIpLocation = mockk(relaxed = true)
@@ -83,66 +71,62 @@ class ConnectViewModelTest {
// In App Notifications
private val mockInAppNotificationController: InAppNotificationController = mockk()
- // Relay list use case
- private val mockRelayListUseCase: RelayListUseCase = mockk()
+ // Select location use case
+ private val mockSelectedLocationTitleUseCase: SelectedLocationTitleUseCase = mockk()
// Payment use case
private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true)
- // Event notifiers
- private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected())
- private val eventNotifierTunnelRealState =
- EventNotifier<TunnelState>(TunnelState.Disconnected())
-
// Flows
- private val selectedRelayItemFlow = MutableStateFlow<RelayItem?>(null)
+ private val tunnelState = MutableStateFlow<TunnelState>(TunnelState.Disconnected())
+ private val selectedRelayItemFlow = MutableStateFlow<String?>(null)
// Out Of Time Use Case
private val outOfTimeUseCase: OutOfTimeUseCase = mockk()
private val outOfTimeViewFlow = MutableStateFlow(false)
+ // Last known location
+ private val mockLastKnownLocationUseCase: LastKnownLocationUseCase = mockk()
+
+ // VpnPermissionRepository
+ private val mockVpnPermissionRepository: VpnPermissionRepository = mockk(relaxed = true)
+
@BeforeEach
fun setup() {
- mockkStatic(CACHE_EXTENSION_CLASS)
- mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS)
-
- mockAppVersionInfoCache =
- mockk<AppVersionInfoCache>().apply {
- every { appVersionCallbackFlow() } returns versionInfo
- }
+ mockkStatic(TUNNEL_ENDPOINT_EXTENSIONS)
+ mockkStatic(GEO_IP_LOCATIONS_EXTENSIONS)
every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
- every { mockServiceConnectionContainer.appVersionInfoCache } returns mockAppVersionInfoCache
- every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
- every { mockAccountRepository.accountExpiryState } returns accountExpiryState
+ every { mockAccountRepository.accountData } returns accountExpiryState
- every { mockDeviceRepository.deviceState } returns deviceState
+ every { mockDeviceRepository.deviceState } returns device
every { mockInAppNotificationController.notifications } returns notifications
- every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState
- every { mockConnectionProxy.onStateChange } returns eventNotifierTunnelRealState
+ every { mockConnectionProxy.tunnelState } returns tunnelState
- every { mockLocation.country } returns "dummy country"
+ every { mockLastKnownLocationUseCase.lastKnownDisconnectedLocation } returns flowOf(null)
- // Listeners
- every { mockAppVersionInfoCache.onUpdate = any() } answers {}
+ every { mockLocation.country } returns "dummy country"
// Flows
- every { mockRelayListUseCase.selectedRelayItem() } returns selectedRelayItemFlow
+ every { mockSelectedLocationTitleUseCase.selectedLocationTitle() } returns
+ selectedRelayItemFlow
every { outOfTimeUseCase.isOutOfTime } returns outOfTimeViewFlow
viewModel =
ConnectViewModel(
- serviceConnectionManager = mockServiceConnectionManager,
accountRepository = mockAccountRepository,
deviceRepository = mockDeviceRepository,
inAppNotificationController = mockInAppNotificationController,
- relayListUseCase = mockRelayListUseCase,
newDeviceNotificationUseCase = mockk(),
outOfTimeUseCase = outOfTimeUseCase,
paymentUseCase = mockPaymentUseCase,
+ selectedLocationTitleUseCase = mockSelectedLocationTitleUseCase,
+ connectionProxy = mockConnectionProxy,
+ lastKnownLocationUseCase = mockLastKnownLocationUseCase,
+ vpnPermissionRepository = mockVpnPermissionRepository,
isPlayBuild = false
)
}
@@ -164,46 +148,41 @@ class ConnectViewModelTest {
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- eventNotifierTunnelRealState.notify(tunnelRealStateTestItem)
+ tunnelState.emit(tunnelRealStateTestItem)
val result = awaitItem()
- assertEquals(tunnelRealStateTestItem, result.tunnelRealState)
+ assertEquals(tunnelRealStateTestItem, result.tunnelState)
}
}
@Test
- fun `given change in tunnelUiState uiState should emit new tunnelUiState`() = runTest {
- val tunnelUiStateTestItem = TunnelState.Connected(mockk(), mockk())
+ fun `given change in tunnelState uiState should emit new tunnelState`() = runTest {
+ // Arrange
+ val tunnelEndpoint: TunnelEndpoint = mockk()
+ val location: GeoIpLocation = mockk()
+ val tunnelStateTestItem = TunnelState.Connected(tunnelEndpoint, location)
+ every { tunnelEndpoint.toInAddress() } returns mockk(relaxed = true)
+ every { location.toOutAddress() } returns "1.1.1.1"
+ every { location.hostname } returns "hostname"
+ // Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- eventNotifierTunnelUiState.notify(tunnelUiStateTestItem)
+ tunnelState.emit(tunnelStateTestItem)
val result = awaitItem()
- assertEquals(tunnelUiStateTestItem, result.tunnelUiState)
+ assertEquals(tunnelStateTestItem, result.tunnelState)
}
}
@Test
fun `given RelayListUseCase returns new selectedRelayItem uiState should emit new selectedRelayItem`() =
runTest {
- val selectedRelayItem =
- RelayItem.Country(
- name = "Name",
- code = "Code",
- expanded = false,
- cities = emptyList()
- )
- selectedRelayItemFlow.value = selectedRelayItem
+ val selectedRelayItemTitle = "Item"
+ selectedRelayItemFlow.value = selectedRelayItemTitle
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
val result = awaitItem()
- assertEquals(selectedRelayItem, result.selectedRelayItem)
+ assertEquals(selectedRelayItemTitle, result.selectedRelayItemTitle)
}
}
@@ -223,15 +202,13 @@ class ConnectViewModelTest {
// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
- eventNotifierTunnelRealState.notify(TunnelState.Disconnected(null))
+ tunnelState.emit(TunnelState.Disconnected(null))
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Start of with no location
assertNull(awaitItem().location)
// After updated we show latest
- eventNotifierTunnelRealState.notify(TunnelState.Disconnected(locationTestItem))
+ tunnelState.emit(TunnelState.Disconnected(locationTestItem))
assertEquals(locationTestItem, awaitItem().location)
}
}
@@ -245,8 +222,6 @@ class ConnectViewModelTest {
// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
expectNoEvents()
val result = awaitItem()
assertEquals(locationTestItem, result.location)
@@ -255,34 +230,50 @@ class ConnectViewModelTest {
@Test
fun `onDisconnectClick should invoke disconnect on ConnectionProxy`() = runTest {
- val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true)
- every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy
+ // Arrange
+ coEvery { mockConnectionProxy.disconnect() } returns true
+
+ // Act
viewModel.onDisconnectClick()
- verify { mockConnectionProxy.disconnect() }
+
+ // Assert
+ coVerify { mockConnectionProxy.disconnect() }
}
@Test
fun `onReconnectClick should invoke reconnect on ConnectionProxy`() = runTest {
- val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true)
- every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy
+ // Arrange
+ coEvery { mockConnectionProxy.reconnect() } returns true
+
+ // Act
viewModel.onReconnectClick()
- verify { mockConnectionProxy.reconnect() }
+
+ // Assert
+ coVerify { mockConnectionProxy.reconnect() }
}
@Test
fun `onConnectClick should invoke connect on ConnectionProxy`() = runTest {
- val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true)
- every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy
+ // Arrange
+ coEvery { mockConnectionProxy.connect() } returns true.right()
+
+ // Act
viewModel.onConnectClick()
- verify { mockConnectionProxy.connect() }
+
+ // Asser
+ coVerify { mockConnectionProxy.connect() }
}
@Test
fun `onCancelClick should invoke disconnect on ConnectionProxy`() = runTest {
- val mockConnectionProxy: ConnectionProxy = mockk(relaxed = true)
- every { mockServiceConnectionManager.connectionProxy() } returns mockConnectionProxy
+ // Arrange
+ coEvery { mockConnectionProxy.disconnect() } returns true
+
+ // Act
viewModel.onCancelClick()
- verify { mockConnectionProxy.disconnect() }
+
+ // Assert
+ coVerify { mockConnectionProxy.disconnect() }
}
@Test
@@ -292,15 +283,13 @@ class ConnectViewModelTest {
val mockErrorState: ErrorState = mockk()
val expectedConnectNotificationState =
InAppNotification.TunnelStateError(mockErrorState)
- val tunnelUiState = TunnelState.Error(mockErrorState)
+ val tunnelStateError = TunnelState.Error(mockErrorState)
notifications.value = listOf(expectedConnectNotificationState)
// Act, Assert
viewModel.uiState.test {
assertEquals(ConnectUiState.INITIAL, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
- eventNotifierTunnelUiState.notify(tunnelUiState)
+ tunnelState.emit(tunnelStateError)
val result = awaitItem()
assertEquals(expectedConnectNotificationState, result.inAppNotification)
}
@@ -310,10 +299,8 @@ class ConnectViewModelTest {
fun `onShowAccountClick call should result in uiSideEffect emitting OpenAccountManagementPageInBrowser`() =
runTest {
// Arrange
- val mockToken = "4444 5555 6666 7777"
- val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true)
- every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache
- coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken
+ val mockToken = WebsiteAuthToken.fromString("154c4cc94810fddac78398662b7fa0c7")
+ coEvery { mockAccountRepository.getWebsiteAuthToken() } returns mockToken
// Act, Assert
viewModel.uiSideEffect.test {
@@ -332,8 +319,6 @@ class ConnectViewModelTest {
// Act
viewModel.uiState.test {
awaitItem()
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
outOfTimeViewFlow.value = true
awaitItem()
}
@@ -343,8 +328,9 @@ class ConnectViewModelTest {
}
companion object {
- private const val CACHE_EXTENSION_CLASS = "net.mullvad.mullvadvpn.util.CacheExtensionsKt"
- private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS =
- "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt"
+ private const val TUNNEL_ENDPOINT_EXTENSIONS =
+ "net.mullvad.mullvadvpn.util.TunnelEndpointExtensionsKt"
+ private const val GEO_IP_LOCATIONS_EXTENSIONS =
+ "net.mullvad.mullvadvpn.util.GeoIpLocationExtensionsKt"
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModelTest.kt
index 7b14db3ffb..83675794f5 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CreateCustomListDialogViewModelTest.kt
@@ -1,17 +1,22 @@
package net.mullvad.mullvadvpn.viewmodel
import app.cash.turbine.test
+import arrow.core.left
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest
+import net.mullvad.mullvadvpn.compose.communication.Created
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
-import net.mullvad.mullvadvpn.compose.communication.CustomListResult
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.CustomListsError
+import net.mullvad.mullvadvpn.lib.model.CustomListAlreadyExists
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.usecase.customlists.CreateWithLocationsError
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
-import net.mullvad.mullvadvpn.usecase.customlists.CustomListsException
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
@@ -25,13 +30,13 @@ class CreateCustomListDialogViewModelTest {
fun `when successfully creating a list with locations should emit return with result side effect`() =
runTest {
// Arrange
- val expectedResult: CustomListResult.Created = mockk()
+ val expectedResult: Created = mockk()
val customListName = "list"
- val viewModel = createViewModelWithLocationCode("AB")
+ val viewModel = createViewModelWithLocationCode(GeoLocationId.Country("AB"))
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Create>())
- } returns Result.success(expectedResult)
- every { expectedResult.locationName } returns "locationName"
+ } returns expectedResult.right()
+ every { expectedResult.locationNames } returns listOf("locationName")
// Act, Assert
viewModel.uiSideEffect.test {
@@ -46,19 +51,23 @@ class CreateCustomListDialogViewModelTest {
fun `when successfully creating a list without locations should emit with navigate to location screen`() =
runTest {
// Arrange
- val expectedResult: CustomListResult.Created = mockk()
- val customListName = "list"
- val createdId = "1"
- val viewModel = createViewModelWithLocationCode("")
+ val customListName = CustomListName.fromString("list")
+ val createdId = CustomListId("1")
+ val expectedResult =
+ Created(
+ id = createdId,
+ name = customListName,
+ locationNames = emptyList(),
+ undo = CustomListAction.Delete(createdId)
+ )
+ val viewModel = createViewModelWithLocationCode(GeoLocationId.Country("AB"))
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Create>())
- } returns Result.success(expectedResult)
- every { expectedResult.locationName } returns null
- every { expectedResult.id } returns createdId
+ } returns expectedResult.right()
// Act, Assert
viewModel.uiSideEffect.test {
- viewModel.createCustomList(customListName)
+ viewModel.createCustomList(customListName.value)
val sideEffect = awaitItem()
assertIs<CreateCustomListDialogSideEffect.NavigateToCustomListLocationsScreen>(
sideEffect
@@ -70,12 +79,12 @@ class CreateCustomListDialogViewModelTest {
@Test
fun `when failing to creating a list should update ui state with error`() = runTest {
// Arrange
- val expectedError = CustomListsError.CustomListExists
+ val expectedError = CreateWithLocationsError.Create(CustomListAlreadyExists)
val customListName = "list"
- val viewModel = createViewModelWithLocationCode("")
+ val viewModel = createViewModelWithLocationCode(GeoLocationId.Country("AB"))
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Create>())
- } returns Result.failure(CustomListsException(expectedError))
+ } returns expectedError.left()
// Act, Assert
viewModel.uiState.test {
@@ -89,12 +98,12 @@ class CreateCustomListDialogViewModelTest {
fun `given error state when calling clear error then should update to state without error`() =
runTest {
// Arrange
- val expectedError = CustomListsError.CustomListExists
+ val expectedError = CreateWithLocationsError.Create(CustomListAlreadyExists)
val customListName = "list"
- val viewModel = createViewModelWithLocationCode("")
+ val viewModel = createViewModelWithLocationCode(GeoLocationId.Country("AB"))
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Create>())
- } returns Result.failure(CustomListsException(expectedError))
+ } returns expectedError.left()
// Act, Assert
viewModel.uiState.test {
@@ -106,7 +115,7 @@ class CreateCustomListDialogViewModelTest {
}
}
- private fun createViewModelWithLocationCode(locationCode: String) =
+ private fun createViewModelWithLocationCode(locationCode: GeoLocationId) =
CreateCustomListDialogViewModel(
locationCode = locationCode,
customListActionUseCase = mockCustomListActionUseCase
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt
index d21789d36f..321e2d53b5 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt
@@ -1,6 +1,7 @@
package net.mullvad.mullvadvpn.viewmodel
import app.cash.turbine.test
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
@@ -8,15 +9,21 @@ import kotlin.test.assertIs
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
-import net.mullvad.mullvadvpn.compose.communication.CustomListResult
+import net.mullvad.mullvadvpn.compose.communication.LocationsChanged
import net.mullvad.mullvadvpn.compose.state.CustomListLocationsUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
-import net.mullvad.mullvadvpn.model.Ownership
-import net.mullvad.mullvadvpn.relaylist.RelayItem
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.Provider
+import net.mullvad.mullvadvpn.lib.model.ProviderId
+import net.mullvad.mullvadvpn.lib.model.RelayItem
import net.mullvad.mullvadvpn.relaylist.descendants
-import net.mullvad.mullvadvpn.usecase.RelayListUseCase
+import net.mullvad.mullvadvpn.repository.RelayListRepository
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
+import net.mullvad.mullvadvpn.usecase.customlists.CustomListRelayItemsUseCase
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -24,23 +31,31 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class CustomListLocationsViewModelTest {
- private val mockRelayListUseCase: RelayListUseCase = mockk()
+ private val mockRelayListRepository: RelayListRepository = mockk()
private val mockCustomListUseCase: CustomListActionUseCase = mockk()
+ private val mockCustomListRelayItemsUseCase: CustomListRelayItemsUseCase = mockk()
- private val relayListFlow = MutableStateFlow<List<RelayItem.Country>>(emptyList())
- private val customListFlow = MutableStateFlow<List<RelayItem.CustomList>>(emptyList())
+ private val relayListFlow = MutableStateFlow<List<RelayItem.Location.Country>>(emptyList())
+ private val selectedLocationsFlow = MutableStateFlow<List<RelayItem.Location>>(emptyList())
@BeforeEach
fun setup() {
- every { mockRelayListUseCase.fullRelayList() } returns relayListFlow
- every { mockRelayListUseCase.customLists() } returns customListFlow
+ every { mockRelayListRepository.relayList } returns relayListFlow
+ every { mockCustomListRelayItemsUseCase.getRelayItemLocationsForCustomList(any()) } returns
+ selectedLocationsFlow
}
@Test
- fun `given new list false state should return new list false`() = runTest {
+ fun `given new list false state uiState newList should be false`() = runTest {
// Arrange
val newList = false
- val viewModel = createViewModel("id", newList)
+ val customList =
+ CustomList(
+ id = CustomListId("id"),
+ name = CustomListName.fromString("name"),
+ locations = emptyList()
+ )
+ val viewModel = createViewModel(customListId = customList.id, newList = newList)
// Act, Assert
viewModel.uiState.test { assertEquals(newList, awaitItem().newList) }
@@ -51,14 +66,7 @@ class CustomListLocationsViewModelTest {
runTest {
// Arrange
val expectedList = DUMMY_COUNTRIES
- val customListId = "id"
- val customListName = "name"
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns emptyList()
- }
- customListFlow.value = listOf(customList)
+ val customListId = CustomListId("id")
val expectedState =
CustomListLocationsUiState.Content.Data(
newList = true,
@@ -75,14 +83,7 @@ class CustomListLocationsViewModelTest {
fun `when selecting parent should select children`() = runTest {
// Arrange
val expectedList = DUMMY_COUNTRIES
- val customListId = "id"
- val customListName = "name"
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns emptyList()
- }
- customListFlow.value = listOf(customList)
+ val customListId = CustomListId("id")
val expectedSelection =
(DUMMY_COUNTRIES + DUMMY_COUNTRIES.flatMap { it.descendants() }).toSet()
val viewModel = createViewModel(customListId, true)
@@ -108,17 +109,11 @@ class CustomListLocationsViewModelTest {
val expectedList = DUMMY_COUNTRIES
val initialSelection =
(DUMMY_COUNTRIES + DUMMY_COUNTRIES.flatMap { it.descendants() }).toSet()
- val customListId = "id"
- val customListName = "name"
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns initialSelection.toList()
- }
- customListFlow.value = listOf(customList)
+ val customListId = CustomListId("id")
val expectedSelection = emptySet<RelayItem>()
- val viewModel = createViewModel(customListId, true)
relayListFlow.value = expectedList
+ selectedLocationsFlow.value = initialSelection.toList()
+ val viewModel = createViewModel(customListId, true)
// Act, Assert
viewModel.uiState.test {
@@ -140,17 +135,11 @@ class CustomListLocationsViewModelTest {
val expectedList = DUMMY_COUNTRIES
val initialSelection =
(DUMMY_COUNTRIES + DUMMY_COUNTRIES.flatMap { it.descendants() }).toSet()
- val customListId = "id"
- val customListName = "name"
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns initialSelection.toList()
- }
- customListFlow.value = listOf(customList)
+ val customListId = CustomListId("id")
val expectedSelection = emptySet<RelayItem>()
- val viewModel = createViewModel(customListId, true)
relayListFlow.value = expectedList
+ selectedLocationsFlow.value = initialSelection.toList()
+ val viewModel = createViewModel(customListId, true)
// Act, Assert
viewModel.uiState.test {
@@ -170,14 +159,7 @@ class CustomListLocationsViewModelTest {
fun `when selecting child should not select parent`() = runTest {
// Arrange
val expectedList = DUMMY_COUNTRIES
- val customListId = "id"
- val customListName = "name"
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns emptyList()
- }
- customListFlow.value = listOf(customList)
+ val customListId = CustomListId("id")
val expectedSelection = DUMMY_COUNTRIES[0].cities[0].relays.toSet()
val viewModel = createViewModel(customListId, true)
relayListFlow.value = expectedList
@@ -200,19 +182,12 @@ class CustomListLocationsViewModelTest {
fun `given new list true when saving successfully should emit close screen side effect`() =
runTest {
// Arrange
- val customListId = "1"
- val customListName = "name"
+ val customListId = CustomListId("1")
val newList = true
- val expectedResult: CustomListResult.LocationsChanged = mockk()
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns DUMMY_COUNTRIES
- }
- customListFlow.value = listOf(customList)
+ val expectedResult: LocationsChanged = mockk()
coEvery {
mockCustomListUseCase.performAction(any<CustomListAction.UpdateLocations>())
- } returns Result.success(expectedResult)
+ } returns expectedResult.right()
val viewModel = createViewModel(customListId, newList)
// Act, Assert
@@ -227,19 +202,12 @@ class CustomListLocationsViewModelTest {
fun `given new list false when saving successfully should emit return with result side effect`() =
runTest {
// Arrange
- val customListId = "1"
- val customListName = "name"
+ val customListId = CustomListId("1")
val newList = false
- val expectedResult: CustomListResult.LocationsChanged = mockk()
- val customList: RelayItem.CustomList = mockk {
- every { id } returns customListId
- every { name } returns customListName
- every { locations } returns DUMMY_COUNTRIES
- }
- customListFlow.value = listOf(customList)
+ val expectedResult: LocationsChanged = mockk()
coEvery {
mockCustomListUseCase.performAction(any<CustomListAction.UpdateLocations>())
- } returns Result.success(expectedResult)
+ } returns expectedResult.right()
val viewModel = createViewModel(customListId, newList)
// Act, Assert
@@ -251,42 +219,49 @@ class CustomListLocationsViewModelTest {
}
}
- private fun createViewModel(customListId: String, newList: Boolean) =
- CustomListLocationsViewModel(
+ private fun createViewModel(
+ customListId: CustomListId,
+ newList: Boolean
+ ): CustomListLocationsViewModel {
+ return CustomListLocationsViewModel(
customListId = customListId,
newList = newList,
- relayListUseCase = mockRelayListUseCase,
+ relayListRepository = mockRelayListRepository,
+ customListRelayItemsUseCase = mockCustomListRelayItemsUseCase,
customListActionUseCase = mockCustomListUseCase
)
+ }
companion object {
private val DUMMY_COUNTRIES =
listOf(
- RelayItem.Country(
+ RelayItem.Location.Country(
name = "Sweden",
- code = "SE",
+ id = GeoLocationId.Country("SE"),
expanded = false,
cities =
listOf(
- RelayItem.City(
+ RelayItem.Location.City(
name = "Gothenburg",
- code = "GBG",
expanded = false,
- location = GeographicLocationConstraint.City("SE", "GBG"),
+ id = GeoLocationId.City(GeoLocationId.Country("SE"), "GBG"),
relays =
listOf(
- RelayItem.Relay(
- name = "gbg-1",
- locationName = "GBG gbg-1",
- active = true,
- location =
- GeographicLocationConstraint.Hostname(
- "SE",
- "GBG",
+ RelayItem.Location.Relay(
+ id =
+ GeoLocationId.Hostname(
+ GeoLocationId.City(
+ GeoLocationId.Country("SE"),
+ "GBG"
+ ),
"gbg-1"
),
- providerName = "Provider",
- ownership = Ownership.MullvadOwned
+ active = true,
+ provider =
+ Provider(
+ ProviderId("Provider"),
+ ownership = Ownership.MullvadOwned
+ )
)
)
)
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListsViewModelTest.kt
index 612ae38a3a..ed615fe0af 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListsViewModelTest.kt
@@ -4,13 +4,13 @@ import app.cash.turbine.test
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
import net.mullvad.mullvadvpn.compose.state.CustomListsUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.relaylist.RelayItem
-import net.mullvad.mullvadvpn.usecase.RelayListUseCase
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.repository.CustomListsRepository
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -18,15 +18,15 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class CustomListsViewModelTest {
- private val mockRelayListUseCase: RelayListUseCase = mockk(relaxed = true)
+ private val mockCustomListsRepository: CustomListsRepository = mockk(relaxed = true)
private val mockCustomListsActionUseCase: CustomListActionUseCase = mockk(relaxed = true)
@Test
fun `given custom list from relay list use case should be in state`() = runTest {
// Arrange
- val customLists: List<RelayItem.CustomList> = mockk()
+ val customLists: List<CustomList> = mockk()
val expectedState = CustomListsUiState.Content(customLists)
- every { mockRelayListUseCase.customLists() } returns flowOf(customLists)
+ every { mockCustomListsRepository.customLists } returns MutableStateFlow(customLists)
val viewModel = createViewModel()
// Act, Assert
@@ -48,7 +48,7 @@ class CustomListsViewModelTest {
private fun createViewModel() =
CustomListsViewModel(
- relayListUseCase = mockRelayListUseCase,
+ customListsRepository = mockCustomListsRepository,
customListActionUseCase = mockCustomListsActionUseCase
)
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModelTest.kt
index 9f7f3f1f0b..6356719c42 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteCustomListConfirmationViewModelTest.kt
@@ -1,13 +1,15 @@
package net.mullvad.mullvadvpn.viewmodel
import app.cash.turbine.test
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.mockk
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
-import net.mullvad.mullvadvpn.compose.communication.CustomListResult
+import net.mullvad.mullvadvpn.compose.communication.Deleted
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
+import net.mullvad.mullvadvpn.lib.model.CustomListId
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
@@ -20,11 +22,11 @@ class DeleteCustomListConfirmationViewModelTest {
@Test
fun `when successfully deleting a list should emit return with result side effect`() = runTest {
// Arrange
- val expectedResult: CustomListResult.Deleted = mockk()
+ val expectedResult: Deleted = mockk()
val viewModel = createViewModel()
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Delete>())
- } returns Result.success(expectedResult)
+ } returns expectedResult.right()
// Act, Assert
viewModel.uiSideEffect.test {
@@ -37,7 +39,7 @@ class DeleteCustomListConfirmationViewModelTest {
private fun createViewModel() =
DeleteCustomListConfirmationViewModel(
- customListId = "1",
+ customListId = CustomListId("1"),
customListActionUseCase = mockCustomListActionUseCase
)
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt
index 11244e9df4..b63f59b302 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceRevokedViewModelTest.kt
@@ -3,27 +3,22 @@ package net.mullvad.mullvadvpn.viewmodel
import app.cash.turbine.test
import io.mockk.MockKAnnotations
import io.mockk.Runs
+import io.mockk.coEvery
+import io.mockk.coVerify
+import io.mockk.coVerifyOrder
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.mockk
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
-import io.mockk.verify
-import io.mockk.verifyOrder
-import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.DeviceRevokedUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.repository.AccountRepository
-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.util.EventNotifier
-import net.mullvad.talpid.util.callbackFlowFromSubscription
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
@@ -35,23 +30,21 @@ class DeviceRevokedViewModelTest {
@MockK private lateinit var mockedAccountRepository: AccountRepository
- @MockK private lateinit var mockedServiceConnectionManager: ServiceConnectionManager
-
- private val serviceConnectionState =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+ @MockK private lateinit var mockConnectionProxy: ConnectionProxy
private lateinit var viewModel: DeviceRevokedViewModel
+ private val tunnelStateFlow = MutableSharedFlow<TunnelState>()
+
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
- mockkStatic(EVENT_NOTIFIER_EXTENSION_CLASS)
- every { mockedServiceConnectionManager.connectionState } returns serviceConnectionState
+ every { mockConnectionProxy.tunnelState } returns tunnelStateFlow
viewModel =
DeviceRevokedViewModel(
- mockedServiceConnectionManager,
- mockedAccountRepository,
- UnconfinedTestDispatcher()
+ accountRepository = mockedAccountRepository,
+ connectionProxy = mockConnectionProxy,
+ dispatcher = UnconfinedTestDispatcher()
)
}
@@ -61,44 +54,15 @@ class DeviceRevokedViewModelTest {
}
@Test
- fun `when service connection is Disconnected then uiState should be UNKNOWN`() = runTest {
- // Arrange, Act, Assert
- viewModel.uiState.test {
- serviceConnectionState.value = ServiceConnectionState.Disconnected
- assertEquals(DeviceRevokedUiState.UNKNOWN, awaitItem())
- }
- }
-
- @Test
- fun `when service connection is ConnectedNotReady then uiState should be UNKNOWN`() = runTest {
- // Arrange, Act, Assert
- viewModel.uiState.test {
- serviceConnectionState.value = ServiceConnectionState.ConnectedNotReady(mockk())
- assertEquals(DeviceRevokedUiState.UNKNOWN, awaitItem())
- }
- }
-
- @Test
- fun `when service connection is ConnectedReady uiState should be SECURED`() = runTest {
+ fun `when tunnel state is secured uiState should be SECURED`() = runTest {
// Arrange
- val mockedContainer =
- mockk<ServiceConnectionContainer>().apply {
- val eventNotifierMock =
- mockk<EventNotifier<TunnelState>>().apply {
- every { callbackFlowFromSubscription(any()) } returns
- MutableStateFlow(TunnelState.Connected(mockk(), mockk()))
- }
- val mockedConnectionProxy =
- mockk<ConnectionProxy>().apply {
- every { onUiStateChange } returns eventNotifierMock
- }
- every { connectionProxy } returns mockedConnectionProxy
- }
+ val tunnelState: TunnelState = mockk()
+ every { tunnelState.isSecured() } returns true
// Act, Assert
viewModel.uiState.test {
assertEquals(DeviceRevokedUiState.UNKNOWN, awaitItem())
- serviceConnectionState.value = ServiceConnectionState.ConnectedReady(mockedContainer)
+ tunnelStateFlow.emit(tunnelState)
assertEquals(DeviceRevokedUiState.SECURED, awaitItem())
}
}
@@ -106,44 +70,29 @@ class DeviceRevokedViewModelTest {
@Test
fun `onGoToLoginClicked should invoke logout on AccountRepository`() {
// Arrange
- val mockedContainer =
- mockk<ServiceConnectionContainer>().also {
- every { it.connectionProxy.state } returns TunnelState.Disconnected()
- every { it.connectionProxy.disconnect() } just Runs
- every { mockedAccountRepository.logout() } just Runs
- }
- serviceConnectionState.value = ServiceConnectionState.ConnectedReady(mockedContainer)
+ coEvery { mockConnectionProxy.disconnect() } returns true
+ coEvery { mockedAccountRepository.logout() } just Runs
// Act
viewModel.onGoToLoginClicked()
// Assert
- verify { mockedAccountRepository.logout() }
+ coVerify { mockedAccountRepository.logout() }
}
@Test
fun `onGoToLoginClicked should invoke disconnect before logout when connected`() {
// Arrange
- val mockedContainer =
- mockk<ServiceConnectionContainer>().also {
- every { it.connectionProxy.state } returns TunnelState.Connected(mockk(), mockk())
- every { it.connectionProxy.disconnect() } just Runs
- every { mockedAccountRepository.logout() } just Runs
- }
- serviceConnectionState.value = ServiceConnectionState.ConnectedReady(mockedContainer)
+ coEvery { mockConnectionProxy.disconnect() } returns true
+ coEvery { mockedAccountRepository.logout() } just Runs
// Act
viewModel.onGoToLoginClicked()
// Assert
- verifyOrder {
- mockedContainer.connectionProxy.disconnect()
+ coVerifyOrder {
+ mockConnectionProxy.disconnect()
mockedAccountRepository.logout()
}
}
-
- companion object {
- private const val EVENT_NOTIFIER_EXTENSION_CLASS =
- "net.mullvad.talpid.util.EventNotifierExtensionsKt"
- }
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModelTest.kt
index e9592d0336..29afc8de0d 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListNameDialogViewModelTest.kt
@@ -1,16 +1,20 @@
package net.mullvad.mullvadvpn.viewmodel
import app.cash.turbine.test
+import arrow.core.left
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.mockk
import kotlin.test.assertIs
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
-import net.mullvad.mullvadvpn.compose.communication.CustomListResult
+import net.mullvad.mullvadvpn.compose.communication.Renamed
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.CustomListsError
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.NameAlreadyExists
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
-import net.mullvad.mullvadvpn.usecase.customlists.CustomListsException
+import net.mullvad.mullvadvpn.usecase.customlists.RenameError
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Test
@@ -23,13 +27,13 @@ class EditCustomListNameDialogViewModelTest {
@Test
fun `when successfully renamed list should emit return with result side effect`() = runTest {
// Arrange
- val expectedResult: CustomListResult.Renamed = mockk()
- val customListId = "id"
+ val expectedResult: Renamed = mockk()
+ val customListId = CustomListId("id")
val customListName = "list"
val viewModel = createViewModel(customListId, customListName)
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Rename>())
- } returns Result.success(expectedResult)
+ } returns expectedResult.right()
// Act, Assert
viewModel.uiSideEffect.test {
@@ -41,15 +45,15 @@ class EditCustomListNameDialogViewModelTest {
}
@Test
- fun `when failing to creating a list should update ui state with error`() = runTest {
+ fun `when failing to rename a list should update ui state with error`() = runTest {
// Arrange
- val expectedError = CustomListsError.CustomListExists
- val customListId = "id2"
+ val customListId = CustomListId("id2")
val customListName = "list2"
+ val expectedError = RenameError(NameAlreadyExists(customListName))
val viewModel = createViewModel(customListId, customListName)
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Rename>())
- } returns Result.failure(CustomListsException(expectedError))
+ } returns expectedError.left()
// Act, Assert
viewModel.uiState.test {
@@ -63,13 +67,13 @@ class EditCustomListNameDialogViewModelTest {
fun `given error state when calling clear error then should update to state without error`() =
runTest {
// Arrange
- val expectedError = CustomListsError.CustomListExists
- val customListId = "id"
+ val customListId = CustomListId("id")
val customListName = "list"
+ val expectedError = RenameError(NameAlreadyExists(customListName))
val viewModel = createViewModel(customListId, customListName)
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.Rename>())
- } returns Result.failure(CustomListsException(expectedError))
+ } returns expectedError.left()
// Act, Assert
viewModel.uiState.test {
@@ -81,10 +85,10 @@ class EditCustomListNameDialogViewModelTest {
}
}
- private fun createViewModel(customListId: String, initialName: String) =
+ private fun createViewModel(customListId: CustomListId, initialName: String) =
EditCustomListNameDialogViewModel(
customListId = customListId,
- initialName = initialName,
+ initialName = CustomListName.fromString(initialName),
customListActionUseCase = mockCustomListActionUseCase
)
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt
index cbc5ff1c50..c3f233846a 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/EditCustomListViewModelTest.kt
@@ -4,33 +4,33 @@ import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
import kotlin.test.assertIs
-import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.EditCustomListState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.CustomListName
-import net.mullvad.mullvadvpn.relaylist.RelayItem
-import net.mullvad.mullvadvpn.usecase.RelayListUseCase
+import net.mullvad.mullvadvpn.lib.model.CustomList
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.repository.CustomListsRepository
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class EditCustomListViewModelTest {
- private val mockRelayListUseCase: RelayListUseCase = mockk(relaxed = true)
+ private val mockCustomListsRepository: CustomListsRepository = mockk(relaxed = true)
@Test
fun `given a custom list id that does not exists should return not found ui state`() = runTest {
// Arrange
- val customListId = "2"
+ val customListId = CustomListId("2")
val customList =
- RelayItem.CustomList(
- id = "1",
- customListName = CustomListName.fromString("test"),
- expanded = false,
+ CustomList(
+ id = CustomListId("1"),
+ name = CustomListName.fromString("test"),
locations = emptyList()
)
- every { mockRelayListUseCase.customLists() } returns flowOf(listOf(customList))
+ every { mockCustomListsRepository.customLists } returns MutableStateFlow(listOf(customList))
val viewModel = createViewModel(customListId)
// Act, Assert
@@ -43,15 +43,14 @@ class EditCustomListViewModelTest {
@Test
fun `given a custom list id that exists should return content ui state`() = runTest {
// Arrange
- val customListId = "1"
+ val customListId = CustomListId("1")
val customList =
- RelayItem.CustomList(
+ CustomList(
id = customListId,
- customListName = CustomListName.fromString("test"),
- expanded = false,
+ name = CustomListName.fromString("test"),
locations = emptyList()
)
- every { mockRelayListUseCase.customLists() } returns flowOf(listOf(customList))
+ every { mockCustomListsRepository.customLists } returns MutableStateFlow(listOf(customList))
val viewModel = createViewModel(customListId)
// Act, Assert
@@ -64,9 +63,9 @@ class EditCustomListViewModelTest {
}
}
- private fun createViewModel(customListId: String) =
+ private fun createViewModel(customListId: CustomListId) =
EditCustomListViewModel(
customListId = customListId,
- relayListUseCase = mockRelayListUseCase
+ customListsRepository = mockCustomListsRepository
)
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt
index fda88bff79..5333a481be 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModelTest.kt
@@ -2,6 +2,8 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.right
+import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
@@ -15,11 +17,13 @@ import net.mullvad.mullvadvpn.compose.state.toConstraintProviders
import net.mullvad.mullvadvpn.compose.state.toOwnershipConstraint
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.common.test.assertLists
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.Ownership
-import net.mullvad.mullvadvpn.model.Providers
-import net.mullvad.mullvadvpn.relaylist.Provider
-import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.Provider
+import net.mullvad.mullvadvpn.lib.model.ProviderId
+import net.mullvad.mullvadvpn.lib.model.Providers
+import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
+import net.mullvad.mullvadvpn.usecase.AvailableProvidersUseCase
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -27,41 +31,52 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class FilterViewModelTest {
- private val mockRelayListFilterUseCase: RelayListFilterUseCase = mockk(relaxed = true)
+ private val mockAvailableProvidersUseCase: AvailableProvidersUseCase = mockk(relaxed = true)
+ private val mockRelayListFilterRepository: RelayListFilterRepository = mockk()
private lateinit var viewModel: FilterViewModel
private val selectedOwnership =
MutableStateFlow<Constraint<Ownership>>(Constraint.Only(Ownership.MullvadOwned))
private val dummyListOfAllProviders =
listOf(
- Provider("31173", true),
- Provider("100TB", false),
- Provider("Blix", true),
- Provider("Creanova", true),
- Provider("DataPacket", false),
- Provider("HostRoyale", false),
- Provider("hostuniversal", false),
- Provider("iRegister", false),
- Provider("M247", false),
- Provider("Makonix", false),
- Provider("PrivateLayer", false),
- Provider("ptisp", false),
- Provider("Qnax", false),
- Provider("Quadranet", false),
- Provider("techfutures", false),
- Provider("Tzulo", false),
- Provider("xtom", false)
+ Provider(ProviderId("31173"), Ownership.MullvadOwned),
+ Provider(ProviderId("100TB"), Ownership.Rented),
+ Provider(ProviderId("Blix"), Ownership.MullvadOwned),
+ Provider(ProviderId("Creanova"), Ownership.MullvadOwned),
+ Provider(ProviderId("DataPacket"), Ownership.Rented),
+ Provider(ProviderId("HostRoyale"), Ownership.Rented),
+ Provider(ProviderId("hostuniversal"), Ownership.Rented),
+ Provider(ProviderId("iRegister"), Ownership.Rented),
+ Provider(ProviderId("M247"), Ownership.Rented),
+ Provider(ProviderId("Makonix"), Ownership.Rented),
+ Provider(ProviderId("PrivateLayer"), Ownership.Rented),
+ Provider(ProviderId("ptisp"), Ownership.Rented),
+ Provider(ProviderId("Qnax"), Ownership.Rented),
+ Provider(ProviderId("Quadranet"), Ownership.Rented),
+ Provider(ProviderId("techfutures"), Ownership.Rented),
+ Provider(ProviderId("Tzulo"), Ownership.Rented),
+ Provider(ProviderId("xtom"), Ownership.Rented)
)
private val mockSelectedProviders: List<Provider> =
- listOf(Provider("31173", true), Provider("Blix", true), Provider("Creanova", true))
+ listOf(
+ Provider(ProviderId("31173"), Ownership.MullvadOwned),
+ Provider(ProviderId("Blix"), Ownership.MullvadOwned),
+ Provider(ProviderId("Creanova"), Ownership.MullvadOwned)
+ )
@BeforeEach
fun setup() {
- every { mockRelayListFilterUseCase.selectedOwnership() } returns selectedOwnership
- every { mockRelayListFilterUseCase.availableProviders() } returns
+ every { mockRelayListFilterRepository.selectedOwnership } returns selectedOwnership
+ every { mockAvailableProvidersUseCase.availableProviders() } returns
flowOf(dummyListOfAllProviders)
- every { mockRelayListFilterUseCase.selectedProviders() } returns
- flowOf(Constraint.Only(Providers(mockSelectedProviders.map { it.name }.toHashSet())))
- viewModel = FilterViewModel(mockRelayListFilterUseCase)
+ every { mockRelayListFilterRepository.selectedProviders } returns
+ MutableStateFlow(
+ Constraint.Only(Providers(mockSelectedProviders.map { it.providerId }.toSet()))
+ )
+ viewModel =
+ FilterViewModel(
+ availableProvidersUseCase = mockAvailableProvidersUseCase,
+ relayListFilterRepository = mockRelayListFilterRepository
+ )
}
@AfterEach
@@ -87,7 +102,7 @@ class FilterViewModelTest {
fun `setSelectionProvider should emit uiState where selectedProviders include the selected provider`() =
runTest {
// Arrange
- val mockSelectedProvidersList = Provider("ptisp", false)
+ val mockSelectedProvidersList = Provider(ProviderId("ptisp"), Ownership.Rented)
// Assert
viewModel.uiState.test {
assertLists(awaitItem().selectedProviders, mockSelectedProviders)
@@ -120,11 +135,19 @@ class FilterViewModelTest {
val mockOwnership = Ownership.MullvadOwned.toOwnershipConstraint()
val mockSelectedProviders =
mockSelectedProviders.toConstraintProviders(dummyListOfAllProviders)
+ coEvery {
+ mockRelayListFilterRepository.updateSelectedOwnershipAndProviderFilter(
+ mockOwnership,
+ mockSelectedProviders
+ )
+ } returns Unit.right()
+
// Act
viewModel.onApplyButtonClicked()
+
// Assert
coVerify {
- mockRelayListFilterUseCase.updateOwnershipAndProviderFilter(
+ mockRelayListFilterRepository.updateSelectedOwnershipAndProviderFilter(
mockOwnership,
mockSelectedProviders
)
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 3271fe57eb..d6eee6d941 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
@@ -3,11 +3,15 @@ package net.mullvad.mullvadvpn.viewmodel
import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test
import app.cash.turbine.turbineScope
+import arrow.core.left
+import arrow.core.right
import io.mockk.MockKAnnotations
import io.mockk.coEvery
+import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
-import io.mockk.verify
+import io.mockk.mockk
+import kotlin.test.assertIs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -19,14 +23,10 @@ import net.mullvad.mullvadvpn.compose.state.LoginState.Loading
import net.mullvad.mullvadvpn.compose.state.LoginState.Success
import net.mullvad.mullvadvpn.compose.state.LoginUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.AccountCreationResult
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.AccountHistory
-import net.mullvad.mullvadvpn.model.AccountToken
-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.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.LoginAccountError
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
import net.mullvad.mullvadvpn.usecase.ConnectivityUseCase
import net.mullvad.mullvadvpn.usecase.NewDeviceNotificationUseCase
import org.joda.time.DateTime
@@ -40,27 +40,23 @@ class LoginViewModelTest {
@MockK private lateinit var connectivityUseCase: ConnectivityUseCase
@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)
@BeforeEach
fun setup() {
-
Dispatchers.setMain(UnconfinedTestDispatcher())
MockKAnnotations.init(this, relaxUnitFun = true)
every { connectivityUseCase.isInternetAvailable() } returns true
- every { mockedAccountRepository.accountHistory } returns accountHistoryTestEvents
every { mockedNewDeviceNotificationUseCase.newDeviceCreated() } returns Unit
+ coEvery { mockedAccountRepository.fetchAccountHistory() } returns null
loginViewModel =
LoginViewModel(
- mockedAccountRepository,
- mockedDeviceRepository,
- mockedNewDeviceNotificationUseCase,
- connectivityUseCase,
+ accountRepository = mockedAccountRepository,
+ newDeviceNotificationUseCase = mockedNewDeviceNotificationUseCase,
+ connectivityUseCase = connectivityUseCase,
UnconfinedTestDispatcher()
)
}
@@ -97,8 +93,7 @@ class LoginViewModelTest {
// Arrange
val uiStates = loginViewModel.uiState.testIn(backgroundScope)
val sideEffects = loginViewModel.uiSideEffect.testIn(backgroundScope)
- coEvery { mockedAccountRepository.createAccount() } returns
- AccountCreationResult.Success(DUMMY_ACCOUNT_TOKEN)
+ coEvery { mockedAccountRepository.createAccount() } returns DUMMY_ACCOUNT_TOKEN.right()
// Act, Assert
uiStates.skipDefaultItem()
@@ -114,13 +109,13 @@ class LoginViewModelTest {
// Arrange
val uiStates = loginViewModel.uiState.testIn(backgroundScope)
val sideEffects = loginViewModel.uiSideEffect.testIn(backgroundScope)
- coEvery { mockedAccountRepository.login(any()) } returns LoginResult.Ok
- coEvery { mockedAccountRepository.accountExpiryState } returns
- MutableStateFlow(AccountExpiry.Available(DateTime.now().plusDays(3)))
+ coEvery { mockedAccountRepository.login(any()) } returns Unit.right()
+ coEvery { mockedAccountRepository.accountData } returns
+ MutableStateFlow(AccountData(mockk(relaxed = true), DateTime.now().plusDays(3)))
// Act, Assert
uiStates.skipDefaultItem()
- loginViewModel.login(DUMMY_ACCOUNT_TOKEN)
+ loginViewModel.login(DUMMY_ACCOUNT_TOKEN.value)
assertEquals(Loading.LoggingIn, uiStates.awaitItem().loginState)
assertEquals(Success, uiStates.awaitItem().loginState)
assertEquals(LoginUiSideEffect.NavigateToConnect, sideEffects.awaitItem())
@@ -131,11 +126,12 @@ class LoginViewModelTest {
fun `given invalid account when logging in then show invalid credentials`() = runTest {
loginViewModel.uiState.test {
// Arrange
- coEvery { mockedAccountRepository.login(any()) } returns LoginResult.InvalidAccount
+ coEvery { mockedAccountRepository.login(any()) } returns
+ LoginAccountError.InvalidAccount.left()
// Act, Assert
skipDefaultItem()
- loginViewModel.login(DUMMY_ACCOUNT_TOKEN)
+ loginViewModel.login(DUMMY_ACCOUNT_TOKEN.value)
assertEquals(Loading.LoggingIn, awaitItem().loginState)
assertEquals(Idle(loginError = LoginError.InvalidCredentials), awaitItem().loginState)
}
@@ -148,23 +144,15 @@ class LoginViewModelTest {
// Arrange
val uiStates = loginViewModel.uiState.testIn(backgroundScope)
val sideEffects = loginViewModel.uiSideEffect.testIn(backgroundScope)
- coEvery {
- mockedDeviceRepository.refreshAndAwaitDeviceListWithTimeout(
- any(),
- any(),
- any(),
- any()
- )
- } returns DeviceListEvent.Available(DUMMY_ACCOUNT_TOKEN, listOf())
coEvery { mockedAccountRepository.login(any()) } returns
- LoginResult.MaxDevicesReached
+ LoginAccountError.MaxDevicesReached(DUMMY_ACCOUNT_TOKEN).left()
// Act, Assert
uiStates.skipDefaultItem()
- loginViewModel.login(DUMMY_ACCOUNT_TOKEN)
+ loginViewModel.login(DUMMY_ACCOUNT_TOKEN.value)
assertEquals(Loading.LoggingIn, uiStates.awaitItem().loginState)
assertEquals(
- LoginUiSideEffect.TooManyDevices(AccountToken(DUMMY_ACCOUNT_TOKEN)),
+ LoginUiSideEffect.TooManyDevices(DUMMY_ACCOUNT_TOKEN),
sideEffects.awaitItem()
)
}
@@ -174,11 +162,12 @@ class LoginViewModelTest {
fun `given RpcError when logging in then show unknown error with message`() = runTest {
loginViewModel.uiState.test {
// Arrange
- coEvery { mockedAccountRepository.login(any()) } returns LoginResult.RpcError
+ coEvery { mockedAccountRepository.login(any()) } returns
+ LoginAccountError.RpcError.left()
// Act, Assert
skipDefaultItem()
- loginViewModel.login(DUMMY_ACCOUNT_TOKEN)
+ loginViewModel.login(DUMMY_ACCOUNT_TOKEN.value)
assertEquals(Loading.LoggingIn, awaitItem().loginState)
assertEquals(
Idle(LoginError.Unknown(EXPECTED_RPC_ERROR_MESSAGE)),
@@ -188,31 +177,32 @@ class LoginViewModelTest {
}
@Test
- fun `given OtherError when logging in then show unknown error with message`() = runTest {
+ fun `given unknown error when logging in then show unknown error with message`() = runTest {
loginViewModel.uiState.test {
// Arrange
- coEvery { mockedAccountRepository.login(any()) } returns LoginResult.OtherError
+ coEvery { mockedAccountRepository.login(any()) } returns
+ LoginAccountError.Unknown(mockk()).left()
// Act, Assert
skipDefaultItem()
- loginViewModel.login(DUMMY_ACCOUNT_TOKEN)
+ loginViewModel.login(DUMMY_ACCOUNT_TOKEN.value)
assertEquals(Loading.LoggingIn, awaitItem().loginState)
- assertEquals(
- Idle(LoginError.Unknown(EXPECTED_OTHER_ERROR_MESSAGE)),
- awaitItem().loginState
- )
+ val loginState = awaitItem().loginState
+ assertIs<Idle>(loginState)
+ assertIs<LoginError.Unknown>(loginState.loginError)
}
}
@Test
fun `on new accountHistory emission uiState should include lastUsedAccount matching accountHistory`() =
runTest {
+ // Arrange
+ coEvery { mockedAccountRepository.fetchAccountHistory() } returns DUMMY_ACCOUNT_TOKEN
+
+ // Act, Assert
loginViewModel.uiState.test {
- // Act, Assert
- skipDefaultItem()
- accountHistoryTestEvents.emit(AccountHistory.Available(DUMMY_ACCOUNT_TOKEN))
assertEquals(
- LoginUiState.INITIAL.copy(lastUsedAccount = AccountToken(DUMMY_ACCOUNT_TOKEN)),
+ LoginUiState.INITIAL.copy(lastUsedAccount = DUMMY_ACCOUNT_TOKEN),
awaitItem()
)
}
@@ -222,7 +212,7 @@ class LoginViewModelTest {
fun `clearAccountHistory should invoke clearAccountHistory on AccountRepository`() = runTest {
// Act, Assert
loginViewModel.clearAccountHistory()
- verify { mockedAccountRepository.clearAccountHistory() }
+ coVerify { mockedAccountRepository.clearAccountHistory() }
}
private suspend fun <T> ReceiveTurbine<T>.skipDefaultItem() where T : Any? {
@@ -230,8 +220,7 @@ class LoginViewModelTest {
}
companion object {
- private const val DUMMY_ACCOUNT_TOKEN = "DUMMY"
+ private val DUMMY_ACCOUNT_TOKEN = AccountToken("DUMMY")
private const val EXPECTED_RPC_ERROR_MESSAGE = "RpcError"
- private const val EXPECTED_OTHER_ERROR_MESSAGE = "OtherError"
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt
index e489c01d41..bd26effe82 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModelTest.kt
@@ -8,34 +8,28 @@ import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkAll
-import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
-import net.mullvad.mullvadvpn.compose.state.OutOfTimeUiState
import net.mullvad.mullvadvpn.compose.state.PaymentState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.common.test.assertLists
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.mullvadvpn.repository.DeviceRepository
-import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache
-import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
+import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
-import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
-import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
import net.mullvad.mullvadvpn.usecase.OutOfTimeUseCase
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
-import net.mullvad.talpid.util.EventNotifier
import org.joda.time.DateTime
import org.joda.time.ReadableInstant
import org.junit.jupiter.api.AfterEach
@@ -47,23 +41,21 @@ import org.junit.jupiter.api.extension.ExtendWith
class OutOfTimeViewModelTest {
private val serviceConnectionStateFlow =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
- private val accountExpiryStateFlow = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing)
- private val deviceStateFlow = MutableStateFlow<DeviceState>(DeviceState.Initial)
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Unbound)
+ private val accountExpiryStateFlow = MutableStateFlow<AccountData?>(null)
+ private val accountStateFlow = MutableStateFlow<DeviceState?>(null)
private val paymentAvailabilityFlow = MutableStateFlow<PaymentAvailability?>(null)
private val purchaseResultFlow = MutableStateFlow<PurchaseResult?>(null)
private val outOfTimeFlow = MutableStateFlow(true)
- // Service connections
- private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+ // Connection Proxy
private val mockConnectionProxy: ConnectionProxy = mockk()
// Event notifiers
- private val eventNotifierTunnelRealState =
- EventNotifier<TunnelState>(TunnelState.Disconnected())
+ private val tunnelState = MutableStateFlow<TunnelState>(TunnelState.Disconnected())
private val mockAccountRepository: AccountRepository = mockk(relaxed = true)
- private val mockDeviceRepository: DeviceRepository = mockk()
+ private val mockDeviceRepository: DeviceRepository = mockk(relaxed = true)
private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true)
private val mockOutOfTimeUseCase: OutOfTimeUseCase = mockk(relaxed = true)
@@ -72,18 +64,15 @@ class OutOfTimeViewModelTest {
@BeforeEach
fun setup() {
- mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS)
mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS)
every { mockServiceConnectionManager.connectionState } returns serviceConnectionStateFlow
- every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
+ every { mockConnectionProxy.tunnelState } returns tunnelState
- every { mockConnectionProxy.onStateChange } returns eventNotifierTunnelRealState
+ every { mockAccountRepository.accountData } returns accountExpiryStateFlow
- every { mockAccountRepository.accountExpiryState } returns accountExpiryStateFlow
-
- every { mockDeviceRepository.deviceState } returns deviceStateFlow
+ every { mockDeviceRepository.deviceState } returns accountStateFlow
coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResultFlow
@@ -94,10 +83,10 @@ class OutOfTimeViewModelTest {
viewModel =
OutOfTimeViewModel(
accountRepository = mockAccountRepository,
- serviceConnectionManager = mockServiceConnectionManager,
deviceRepository = mockDeviceRepository,
paymentUseCase = mockPaymentUseCase,
outOfTimeUseCase = mockOutOfTimeUseCase,
+ connectionProxy = mockConnectionProxy,
pollAccountExpiry = false,
isPlayBuild = false
)
@@ -112,10 +101,8 @@ class OutOfTimeViewModelTest {
@Test
fun `when clicking on site payment then open website account view`() = runTest {
// Arrange
- val mockToken = "4444 5555 6666 7777"
- val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true)
- every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache
- coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken
+ val mockToken = WebsiteAuthToken.fromString("154c4cc94810fddac78398662b7fa0c7")
+ coEvery { mockAccountRepository.getWebsiteAuthToken() } returns mockToken
// Act, Assert
viewModel.uiSideEffect.test {
@@ -133,10 +120,9 @@ class OutOfTimeViewModelTest {
// Act, Assert
viewModel.uiState.test {
- assertEquals(OutOfTimeUiState(deviceName = ""), awaitItem())
- eventNotifierTunnelRealState.notify(tunnelRealStateTestItem)
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ // Default item
+ awaitItem()
+ tunnelState.emit(tunnelRealStateTestItem)
val result = awaitItem()
assertEquals(tunnelRealStateTestItem, result.tunnelState)
}
@@ -160,14 +146,13 @@ class OutOfTimeViewModelTest {
@Test
fun `onDisconnectClick should invoke disconnect on ConnectionProxy`() = runTest {
// Arrange
- val mockProxy: ConnectionProxy = mockk(relaxed = true)
- every { mockServiceConnectionManager.connectionProxy() } returns mockProxy
+ coEvery { mockConnectionProxy.disconnect() } returns true
// Act
viewModel.onDisconnectClick()
// Assert
- verify { mockProxy.disconnect() }
+ coVerify { mockConnectionProxy.disconnect() }
}
@Test
@@ -176,8 +161,6 @@ class OutOfTimeViewModelTest {
// Arrange
val productsUnavailable = PaymentAvailability.ProductsUnavailable
paymentAvailabilityFlow.value = productsUnavailable
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -192,8 +175,6 @@ class OutOfTimeViewModelTest {
// Arrange
val paymentAvailabilityError = PaymentAvailability.Error.Other(mockk())
paymentAvailabilityFlow.value = paymentAvailabilityError
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -208,8 +189,6 @@ class OutOfTimeViewModelTest {
// Arrange
val paymentAvailabilityError = PaymentAvailability.Error.BillingUnavailable
paymentAvailabilityFlow.value = paymentAvailabilityError
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -226,8 +205,6 @@ class OutOfTimeViewModelTest {
val expectedProductList = listOf(mockProduct)
val productsAvailable = PaymentAvailability.ProductsAvailable(listOf(mockProduct))
paymentAvailabilityFlow.value = productsAvailable
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -238,14 +215,12 @@ class OutOfTimeViewModelTest {
}
@Test
- fun `onClosePurchaseResultDialog with success should invoke fetchAccountExpiry on AccountRepository`() {
- // Arrange
-
+ fun `onClosePurchaseResultDialog with success should invoke getAccountData on AccountRepository`() {
// Act
viewModel.onClosePurchaseResultDialog(success = true)
// Assert
- verify { mockAccountRepository.fetchAccountExpiry() }
+ coVerify { mockAccountRepository.getAccountData() }
}
@Test
@@ -282,8 +257,6 @@ class OutOfTimeViewModelTest {
}
companion object {
- private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS =
- "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt"
private const val PURCHASE_RESULT_EXTENSIONS_CLASS =
"net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt"
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ResetServerIpOverridesConfirmationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ResetServerIpOverridesConfirmationViewModelTest.kt
index 9be365e7ae..17394c39db 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ResetServerIpOverridesConfirmationViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ResetServerIpOverridesConfirmationViewModelTest.kt
@@ -2,17 +2,17 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.right
import io.mockk.coEvery
-import io.mockk.every
+import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.unmockkAll
-import io.mockk.verify
import kotlin.test.assertEquals
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.RelayOverride
+import net.mullvad.mullvadvpn.lib.model.RelayOverride
import net.mullvad.mullvadvpn.repository.RelayOverridesRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@@ -44,7 +44,7 @@ class ResetServerIpOverridesConfirmationViewModelTest {
@Test
fun `successful clear of override should result in side effect`() = runTest {
- every { mockRelayOverridesRepository.clearAllOverrides() } returns Unit
+ coEvery { mockRelayOverridesRepository.clearAllOverrides() } returns Unit.right()
viewModel.uiSideEffect.test {
viewModel.clearAllOverrides()
assertEquals(
@@ -56,8 +56,8 @@ class ResetServerIpOverridesConfirmationViewModelTest {
@Test
fun `clear overrides should invoke repository`() = runTest {
- every { mockRelayOverridesRepository.clearAllOverrides() } returns Unit
+ coEvery { mockRelayOverridesRepository.clearAllOverrides() } returns Unit.right()
viewModel.clearAllOverrides()
- verify { mockRelayOverridesRepository.clearAllOverrides() }
+ coVerify { mockRelayOverridesRepository.clearAllOverrides() }
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
index 5d0ab5f604..80f62dba4a 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModelTest.kt
@@ -2,42 +2,40 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
-import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
-import io.mockk.runs
import io.mockk.unmockkAll
-import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.communication.CustomListAction
-import net.mullvad.mullvadvpn.compose.communication.CustomListResult
+import net.mullvad.mullvadvpn.compose.communication.LocationsChanged
import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.common.test.assertLists
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.GeographicLocationConstraint
-import net.mullvad.mullvadvpn.model.LocationConstraint
-import net.mullvad.mullvadvpn.model.Ownership
-import net.mullvad.mullvadvpn.model.Providers
-import net.mullvad.mullvadvpn.relaylist.Provider
-import net.mullvad.mullvadvpn.relaylist.RelayItem
-import net.mullvad.mullvadvpn.relaylist.RelayList
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.CustomListId
+import net.mullvad.mullvadvpn.lib.model.CustomListName
+import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.Ownership
+import net.mullvad.mullvadvpn.lib.model.Provider
+import net.mullvad.mullvadvpn.lib.model.Providers
+import net.mullvad.mullvadvpn.lib.model.RelayItem
+import net.mullvad.mullvadvpn.lib.model.RelayItemId
import net.mullvad.mullvadvpn.relaylist.descendants
import net.mullvad.mullvadvpn.relaylist.filterOnSearchTerm
-import net.mullvad.mullvadvpn.relaylist.toLocationConstraint
-import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
-import net.mullvad.mullvadvpn.ui.serviceconnection.connectionProxy
-import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase
-import net.mullvad.mullvadvpn.usecase.RelayListUseCase
+import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
+import net.mullvad.mullvadvpn.repository.RelayListRepository
+import net.mullvad.mullvadvpn.usecase.AvailableProvidersUseCase
+import net.mullvad.mullvadvpn.usecase.FilteredRelayListUseCase
import net.mullvad.mullvadvpn.usecase.customlists.CustomListActionUseCase
+import net.mullvad.mullvadvpn.usecase.customlists.CustomListsRelayItemUseCase
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -46,36 +44,44 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class SelectLocationViewModelTest {
- private val mockRelayListFilterUseCase: RelayListFilterUseCase = mockk(relaxed = true)
- private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
- private lateinit var viewModel: SelectLocationViewModel
- private val relayListWithSelectionFlow =
- MutableStateFlow(RelayList(emptyList(), emptyList(), emptyList(), null))
- private val mockRelayListUseCase: RelayListUseCase = mockk()
+ private val mockRelayListFilterRepository: RelayListFilterRepository = mockk()
+ private val mockAvailableProvidersUseCase: AvailableProvidersUseCase = mockk(relaxed = true)
private val mockCustomListActionUseCase: CustomListActionUseCase = mockk(relaxed = true)
- private val selectedOwnership = MutableStateFlow<Constraint<Ownership>>(Constraint.Any())
- private val selectedProvider = MutableStateFlow<Constraint<Providers>>(Constraint.Any())
- private val allProvider = MutableStateFlow<List<Provider>>(emptyList())
+ private val mockCustomListsRelayItemUseCase: CustomListsRelayItemUseCase = mockk()
+ private val mockFilteredRelayListUseCase: FilteredRelayListUseCase = mockk()
+ private val mockRelayListRepository: RelayListRepository = mockk()
+
+ private lateinit var viewModel: SelectLocationViewModel
+
+ private val allProviders = MutableStateFlow<List<Provider>>(emptyList())
+ private val selectedOwnership = MutableStateFlow<Constraint<Ownership>>(Constraint.Any)
+ private val selectedProviders = MutableStateFlow<Constraint<Providers>>(Constraint.Any)
+ private val selectedRelayItemFlow = MutableStateFlow<Constraint<RelayItemId>>(Constraint.Any)
+ private val filteredRelayList = MutableStateFlow<List<RelayItem.Location.Country>>(emptyList())
+ private val customRelayListItems = MutableStateFlow<List<RelayItem.CustomList>>(emptyList())
@BeforeEach
fun setup() {
- every { mockRelayListFilterUseCase.selectedOwnership() } returns selectedOwnership
- every { mockRelayListFilterUseCase.selectedProviders() } returns selectedProvider
- every { mockRelayListFilterUseCase.availableProviders() } returns allProvider
- every { mockRelayListUseCase.relayListWithSelection() } returns relayListWithSelectionFlow
- every { mockRelayListUseCase.fetchRelayList() } just runs
+ every { mockRelayListFilterRepository.selectedOwnership } returns selectedOwnership
+ every { mockRelayListFilterRepository.selectedProviders } returns selectedProviders
+ every { mockAvailableProvidersUseCase.availableProviders() } returns allProviders
+ every { mockRelayListRepository.selectedLocation } returns selectedRelayItemFlow
+ every { mockFilteredRelayListUseCase.filteredRelayList() } returns filteredRelayList
+ every { mockCustomListsRelayItemUseCase.relayItemCustomLists() } returns
+ customRelayListItems
- mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS)
mockkStatic(RELAY_LIST_EXTENSIONS)
mockkStatic(RELAY_ITEM_EXTENSIONS)
mockkStatic(CUSTOM_LIST_EXTENSIONS)
viewModel =
SelectLocationViewModel(
- mockServiceConnectionManager,
- mockRelayListUseCase,
- mockRelayListFilterUseCase,
- mockCustomListActionUseCase
+ relayListFilterRepository = mockRelayListFilterRepository,
+ availableProvidersUseCase = mockAvailableProvidersUseCase,
+ customListsRelayItemUseCase = mockCustomListsRelayItemUseCase,
+ customListActionUseCase = mockCustomListActionUseCase,
+ filteredRelayListUseCase = mockFilteredRelayListUseCase,
+ relayListRepository = mockRelayListRepository
)
}
@@ -93,12 +99,11 @@ class SelectLocationViewModelTest {
@Test
fun `given relayListWithSelection emits update uiState should contain new update`() = runTest {
// Arrange
- val mockCountries = listOf<RelayItem.Country>(mockk(), mockk())
- val mockCustomList = listOf<RelayItem.CustomList>(mockk(relaxed = true))
- val selectedItem: RelayItem = mockk()
+ val mockCountries = listOf<RelayItem.Location.Country>(mockk(), mockk())
+ val selectedItem: RelayItemId = mockk()
every { mockCountries.filterOnSearchTerm(any(), selectedItem) } returns mockCountries
- relayListWithSelectionFlow.value =
- RelayList(mockCustomList, mockCountries, mockCountries, selectedItem)
+ filteredRelayList.value = mockCountries
+ selectedRelayItemFlow.value = Constraint.Only(selectedItem)
// Act, Assert
viewModel.uiState.test {
@@ -113,12 +118,11 @@ class SelectLocationViewModelTest {
fun `given relayListWithSelection emits update with no selections selectedItem should be null`() =
runTest {
// Arrange
- val mockCustomList = listOf<RelayItem.CustomList>(mockk(relaxed = true))
- val mockCountries = listOf<RelayItem.Country>(mockk(), mockk())
- val selectedItem: RelayItem? = null
+ val mockCountries = listOf<RelayItem.Location.Country>(mockk(), mockk())
+ val selectedItem: RelayItemId? = null
every { mockCountries.filterOnSearchTerm(any(), selectedItem) } returns mockCountries
- relayListWithSelectionFlow.value =
- RelayList(mockCustomList, mockCountries, mockCountries, selectedItem)
+ filteredRelayList.value = mockCountries
+ selectedRelayItemFlow.value = Constraint.Any
// Act, Assert
viewModel.uiState.test {
@@ -132,25 +136,18 @@ class SelectLocationViewModelTest {
@Test
fun `on selectRelay call uiSideEffect should emit CloseScreen and connect`() = runTest {
// Arrange
- val mockRelayItem: RelayItem.Country = mockk()
- val mockLocation: GeographicLocationConstraint.Country = mockk(relaxed = true)
- val mockLocationConstraint: LocationConstraint = mockk()
- val connectionProxyMock: ConnectionProxy = mockk(relaxUnitFun = true)
- every { mockRelayItem.location } returns mockLocation
- every { mockServiceConnectionManager.connectionProxy() } returns connectionProxyMock
- every { mockRelayListUseCase.updateSelectedRelayLocation(mockLocationConstraint) } returns
- Unit
- every { mockRelayItem.toLocationConstraint() } returns mockLocationConstraint
+ val mockRelayItem: RelayItem.Location.Country = mockk()
+ val relayItemId: GeoLocationId.Country = mockk(relaxed = true)
+ every { mockRelayItem.id } returns relayItemId
+ coEvery { mockRelayListRepository.updateSelectedRelayLocation(relayItemId) } returns
+ Unit.right()
// Act, Assert
viewModel.uiSideEffect.test {
viewModel.selectRelay(mockRelayItem)
// Await an empty item
assertEquals(SelectLocationSideEffect.CloseScreen, awaitItem())
- verify {
- connectionProxyMock.connect()
- mockRelayListUseCase.updateSelectedRelayLocation(mockLocationConstraint)
- }
+ coVerify { mockRelayListRepository.updateSelectedRelayLocation(relayItemId) }
}
}
@@ -158,15 +155,15 @@ class SelectLocationViewModelTest {
fun `on onSearchTermInput call uiState should emit with filtered countries`() = runTest {
// Arrange
val mockCustomList = listOf<RelayItem.CustomList>(mockk(relaxed = true))
- val mockCountries = listOf<RelayItem.Country>(mockk(), mockk())
- val selectedItem: RelayItem? = null
- val mockRelayList: List<RelayItem.Country> = mockk(relaxed = true)
+ val mockCountries = listOf<RelayItem.Location.Country>(mockk(), mockk())
+ val selectedItem: RelayItemId? = null
+ val mockRelayList: List<RelayItem.Location.Country> = mockk(relaxed = true)
val mockSearchString = "SEARCH"
every { mockRelayList.filterOnSearchTerm(mockSearchString, selectedItem) } returns
mockCountries
every { mockCustomList.filterOnSearchTerm(mockSearchString) } returns mockCustomList
- relayListWithSelectionFlow.value =
- RelayList(mockCustomList, mockRelayList, mockRelayList, selectedItem)
+ filteredRelayList.value = mockRelayList
+ selectedRelayItemFlow.value = Constraint.Any
// Act, Assert
viewModel.uiState.test {
@@ -188,15 +185,13 @@ class SelectLocationViewModelTest {
fun `when onSearchTermInput returns empty result uiState should return empty list`() = runTest {
// Arrange
val mockCustomList = listOf<RelayItem.CustomList>(mockk(relaxed = true))
- val mockCountries = emptyList<RelayItem.Country>()
- val selectedItem: RelayItem? = null
- val mockRelayList: List<RelayItem.Country> = mockk(relaxed = true)
+ val mockCountries = emptyList<RelayItem.Location.Country>()
+ val selectedItem: RelayItemId? = null
+ val mockRelayList: List<RelayItem.Location.Country> = mockk(relaxed = true)
val mockSearchString = "SEARCH"
every { mockRelayList.filterOnSearchTerm(mockSearchString, selectedItem) } returns
mockCountries
every { mockCustomList.filterOnSearchTerm(mockSearchString) } returns mockCustomList
- relayListWithSelectionFlow.value =
- RelayList(mockCustomList, mockRelayList, mockRelayList, selectedItem)
// Act, Assert
viewModel.uiState.test {
@@ -217,36 +212,30 @@ class SelectLocationViewModelTest {
fun `removeOwnerFilter should invoke use case with Constraint Any Ownership`() = runTest {
// Arrange
val mockSelectedProviders: Constraint<Providers> = mockk()
- every { mockRelayListFilterUseCase.selectedProviders() } returns
+ every { mockRelayListFilterRepository.selectedProviders } returns
MutableStateFlow(mockSelectedProviders)
+ coEvery { mockRelayListFilterRepository.updateSelectedOwnership(Constraint.Any) } returns
+ Unit.right()
// Act
viewModel.removeOwnerFilter()
// Assert
- verify {
- mockRelayListFilterUseCase.updateOwnershipAndProviderFilter(
- any<Constraint.Any<Ownership>>(),
- mockSelectedProviders
- )
- }
+ coVerify { mockRelayListFilterRepository.updateSelectedOwnership(Constraint.Any) }
}
@Test
fun `removeProviderFilter should invoke use case with Constraint Any Provider`() = runTest {
// Arrange
val mockSelectedOwnership: Constraint<Ownership> = mockk()
- every { mockRelayListFilterUseCase.selectedOwnership() } returns
+ every { mockRelayListFilterRepository.selectedOwnership } returns
MutableStateFlow(mockSelectedOwnership)
+ coEvery { mockRelayListFilterRepository.updateSelectedProviders(Constraint.Any) } returns
+ Unit.right()
// Act
viewModel.removeProviderFilter()
// Assert
- verify {
- mockRelayListFilterUseCase.updateOwnershipAndProviderFilter(
- mockSelectedOwnership,
- any<Constraint.Any<Providers>>()
- )
- }
+ coVerify { mockRelayListFilterRepository.updateSelectedProviders(Constraint.Any) }
}
@Test
@@ -264,18 +253,21 @@ class SelectLocationViewModelTest {
@Test
fun `after adding a location to a list should emit location added side effect`() = runTest {
// Arrange
- val expectedResult: CustomListResult.LocationsChanged = mockk()
- val location: RelayItem = mockk {
- every { code } returns "code"
+ val expectedResult: LocationsChanged = mockk()
+ val location: RelayItem.Location.Country = mockk {
+ every { id } returns GeoLocationId.Country("se")
every { descendants() } returns emptyList()
}
- val customList: RelayItem.CustomList = mockk {
- every { id } returns "1"
- every { locations } returns emptyList()
- }
+ val customList =
+ RelayItem.CustomList(
+ id = CustomListId("1"),
+ customListName = CustomListName.fromString("custom"),
+ locations = emptyList(),
+ expanded = false
+ )
coEvery {
mockCustomListActionUseCase.performAction(any<CustomListAction.UpdateLocations>())
- } returns Result.success(expectedResult)
+ } returns expectedResult.right()
// Act, Assert
viewModel.uiSideEffect.test {
@@ -287,8 +279,6 @@ class SelectLocationViewModelTest {
}
companion object {
- private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS =
- "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt"
private const val RELAY_LIST_EXTENSIONS =
"net.mullvad.mullvadvpn.relaylist.RelayListExtensionsKt"
private const val RELAY_ITEM_EXTENSIONS =
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt
index 16e89ac20b..b39d4357de 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ServerIpOverridesViewModelTest.kt
@@ -4,6 +4,8 @@ import android.content.ContentResolver
import android.net.Uri
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.left
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
@@ -17,13 +19,9 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.lib.ipc.Event
-import net.mullvad.mullvadvpn.model.RelayOverride
-import net.mullvad.mullvadvpn.model.SettingsPatchError
+import net.mullvad.mullvadvpn.lib.model.RelayOverride
+import net.mullvad.mullvadvpn.lib.model.SettingsPatchError
import net.mullvad.mullvadvpn.repository.RelayOverridesRepository
-import net.mullvad.mullvadvpn.repository.SettingsRepository
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -33,27 +31,20 @@ import org.junit.jupiter.api.extension.ExtendWith
class ServerIpOverridesViewModelTest {
private lateinit var viewModel: ServerIpOverridesViewModel
- private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
private val mockRelayOverridesRepository: RelayOverridesRepository = mockk()
- private val mockSettingsRepository: SettingsRepository = mockk(relaxed = true)
private val mockContentResolver: ContentResolver = mockk()
private val relayOverrides = MutableStateFlow<List<RelayOverride>?>(null)
- private val serviceConnectionState =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.ConnectedReady(mockk()))
@BeforeEach
fun setup() {
coEvery { mockRelayOverridesRepository.relayOverrides } returns relayOverrides
- coEvery { mockServiceConnectionManager.connectionState } returns serviceConnectionState
mockkStatic(READ_TEXT)
viewModel =
ServerIpOverridesViewModel(
- serviceConnectionManager = mockServiceConnectionManager,
relayOverridesRepository = mockRelayOverridesRepository,
- settingsRepository = mockSettingsRepository,
contentResolver = mockContentResolver
)
}
@@ -80,10 +71,12 @@ class ServerIpOverridesViewModelTest {
@Test
fun `when import is finished we should get side effect`() = runTest {
+ // Arrange
val mockkResult: SettingsPatchError = mockk()
- coEvery { mockSettingsRepository.applySettingsPatch(TEXT_INPUT) } returns
- Event.ApplyJsonSettingsResult(mockkResult)
+ coEvery { mockRelayOverridesRepository.applySettingsPatch(TEXT_INPUT) } returns
+ mockkResult.left()
+ // Act, Assert
viewModel.uiSideEffect.test {
viewModel.importText(TEXT_INPUT)
assertEquals(ServerIpOverridesUiSideEffect.ImportResult(mockkResult), awaitItem())
@@ -92,22 +85,30 @@ class ServerIpOverridesViewModelTest {
@Test
fun `ensure import text invokes repository`() = runTest {
+ // Arrange
+ coEvery { mockRelayOverridesRepository.applySettingsPatch(TEXT_INPUT) } returns Unit.right()
+
+ // Act
viewModel.importText(TEXT_INPUT)
- coVerify { mockSettingsRepository.applySettingsPatch(TEXT_INPUT) }
+ // Assert
+ coVerify { mockRelayOverridesRepository.applySettingsPatch(TEXT_INPUT) }
}
@Test
fun `ensure import file invokes repository`() = runTest {
+ // Arrange
val uri: Uri = mockk()
-
val mockInputStream: InputStream = mockk()
every { mockContentResolver.openInputStream(uri) } returns mockInputStream
every { any<InputStreamReader>().readText() } returns TEXT_INPUT
+ coEvery { mockRelayOverridesRepository.applySettingsPatch(TEXT_INPUT) } returns Unit.right()
+ // Act
viewModel.importFile(uri)
- coVerify { mockSettingsRepository.applySettingsPatch(TEXT_INPUT) }
+ // Assert
+ coVerify { mockRelayOverridesRepository.applySettingsPatch(TEXT_INPUT) }
}
companion object {
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt
index 0eace9ca43..c76e2cd278 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SettingsViewModelTest.kt
@@ -4,21 +4,16 @@ import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
import io.mockk.every
import io.mockk.mockk
-import io.mockk.mockkStatic
import io.mockk.unmockkAll
import kotlin.test.assertEquals
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.repository.DeviceRepository
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
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 net.mullvad.mullvadvpn.ui.serviceconnection.AppVersionInfoRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -28,42 +23,26 @@ import org.junit.jupiter.api.extension.ExtendWith
class SettingsViewModelTest {
private val mockDeviceRepository: DeviceRepository = mockk()
- private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
- private lateinit var mockAppVersionInfoCache: AppVersionInfoCache
- private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+ private val mockAppVersionInfoRepository: AppVersionInfoRepository = mockk()
- private val serviceConnectionState =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
private val versionInfo =
MutableStateFlow(
- VersionInfo(
- currentVersion = null,
- upgradeVersion = null,
- isOutdated = false,
- isSupported = false
- )
+ VersionInfo(currentVersion = "", isSupported = false, suggestedUpgradeVersion = null)
)
private lateinit var viewModel: SettingsViewModel
@BeforeEach
fun setup() {
- mockkStatic(CACHE_EXTENSION_CLASS)
val deviceState = MutableStateFlow<DeviceState>(DeviceState.LoggedOut)
- mockAppVersionInfoCache =
- mockk<AppVersionInfoCache>().apply {
- every { appVersionCallbackFlow() } returns versionInfo
- }
- every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
- every { mockServiceConnectionContainer.appVersionInfoCache } returns mockAppVersionInfoCache
every { mockDeviceRepository.deviceState } returns deviceState
- every { mockAppVersionInfoCache.onUpdate = any() } answers {}
+ every { mockAppVersionInfoRepository.versionInfo() } returns versionInfo
viewModel =
SettingsViewModel(
deviceRepository = mockDeviceRepository,
- serviceConnectionManager = mockServiceConnectionManager,
+ appVersionInfoRepository = mockAppVersionInfoRepository,
isPlayBuild = false
)
}
@@ -87,20 +66,14 @@ class SettingsViewModelTest {
val versionInfoTestItem =
VersionInfo(
currentVersion = "1.0",
- upgradeVersion = "1.0",
- isOutdated = false,
- isSupported = true
+ isSupported = true,
+ suggestedUpgradeVersion = null
)
- every { mockAppVersionInfoCache.version } returns "1.0"
- every { mockAppVersionInfoCache.isSupported } returns true
- every { mockAppVersionInfoCache.isOutdated } returns false
// Act, Assert
viewModel.uiState.test {
awaitItem() // Wait for initial value
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
versionInfo.value = versionInfoTestItem
val result = awaitItem()
assertEquals(false, result.isUpdateAvailable)
@@ -111,16 +84,12 @@ class SettingsViewModelTest {
fun `when AppVersionInfoCache returns isSupported false uiState should return isUpdateAvailable true`() =
runTest {
// Arrange
- every { mockAppVersionInfoCache.isSupported } returns false
- every { mockAppVersionInfoCache.isOutdated } returns false
- every { mockAppVersionInfoCache.version } returns ""
+ val versionInfoTestItem =
+ VersionInfo(currentVersion = "", isSupported = false, suggestedUpgradeVersion = "")
+ versionInfo.value = versionInfoTestItem
// Act, Assert
viewModel.uiState.test {
- awaitItem()
-
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
val result = awaitItem()
assertEquals(true, result.isUpdateAvailable)
}
@@ -130,22 +99,14 @@ class SettingsViewModelTest {
fun `when AppVersionInfoCache returns isOutdated true uiState should return isUpdateAvailable true`() =
runTest {
// Arrange
- every { mockAppVersionInfoCache.isSupported } returns true
- every { mockAppVersionInfoCache.isOutdated } returns true
- every { mockAppVersionInfoCache.version } returns ""
+ val versionInfoTestItem =
+ VersionInfo(currentVersion = "", isSupported = true, suggestedUpgradeVersion = "")
+ versionInfo.value = versionInfoTestItem
// Act, Assert
viewModel.uiState.test {
- awaitItem()
-
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
val result = awaitItem()
assertEquals(true, result.isUpdateAvailable)
}
}
-
- 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/SplitTunnelingViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt
index 11b253e5ea..aa1ccc82f0 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SplitTunnelingViewModelTest.kt
@@ -2,15 +2,13 @@ package net.mullvad.mullvadvpn.viewmodel
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.right
+import io.mockk.coEvery
+import io.mockk.coVerify
import io.mockk.every
-import io.mockk.invoke
-import io.mockk.just
import io.mockk.mockk
-import io.mockk.runs
-import io.mockk.slot
import io.mockk.unmockkAll
import io.mockk.verify
-import io.mockk.verifyAll
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlinx.coroutines.cancel
@@ -21,10 +19,8 @@ import net.mullvad.mullvadvpn.applist.AppData
import net.mullvad.mullvadvpn.applist.ApplicationsProvider
import net.mullvad.mullvadvpn.compose.state.SplitTunnelingUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-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.ui.serviceconnection.SplitTunneling
+import net.mullvad.mullvadvpn.lib.model.AppId
+import net.mullvad.mullvadvpn.repository.SplitTunnelingRepository
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -36,14 +32,16 @@ import org.junit.jupiter.api.extension.ExtendWith
class SplitTunnelingViewModelTest {
private val mockedApplicationsProvider = mockk<ApplicationsProvider>()
- private val mockedSplitTunneling = mockk<SplitTunneling>()
- private val mockedServiceConnectionManager = mockk<ServiceConnectionManager>()
- private val mockedServiceConnectionContainer = mockk<ServiceConnectionContainer>()
+ private val mockedSplitTunnelingRepository = mockk<SplitTunnelingRepository>()
private lateinit var testSubject: SplitTunnelingViewModel
+ private val excludedApps: MutableStateFlow<Set<AppId>> = MutableStateFlow(emptySet())
+ private val enabled: MutableStateFlow<Boolean> = MutableStateFlow(true)
+
@BeforeEach
fun setup() {
- every { mockedSplitTunneling.enabled } returns true
+ every { mockedSplitTunnelingRepository.splitTunnelingEnabled } returns enabled
+ every { mockedSplitTunnelingRepository.excludedApps } returns excludedApps
}
@AfterEach
@@ -66,14 +64,6 @@ class SplitTunnelingViewModelTest {
@Test
fun `empty app list should work`() = runTest {
- every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers
- {
- lambda<(Set<String>) -> Unit>().invoke(emptySet())
- }
- every { mockedSplitTunneling.enabledChange = captureLambda() } answers
- {
- lambda<(Boolean) -> Unit>().invoke(true)
- }
initTestSubject(emptyList())
val expectedState =
SplitTunnelingUiState.ShowAppList(
@@ -89,16 +79,9 @@ class SplitTunnelingViewModelTest {
fun `includedApps and excludedApps should both be included in uiState`() = runTest {
val appExcluded = AppData("test.excluded", 0, "testName1")
val appNotExcluded = AppData("test.not.excluded", 0, "testName2")
- every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers
- {
- lambda<(Set<String>) -> Unit>().invoke(setOf(appExcluded.packageName))
- }
- every { mockedSplitTunneling.enabledChange = captureLambda() } answers
- {
- lambda<(Boolean) -> Unit>().invoke(true)
- }
initTestSubject(listOf(appExcluded, appNotExcluded))
+ excludedApps.value = setOf(AppId(appExcluded.packageName))
val expectedState =
SplitTunnelingUiState.ShowAppList(
@@ -111,29 +94,15 @@ class SplitTunnelingViewModelTest {
testSubject.uiState.test {
val actualState = awaitItem()
assertEquals(expectedState, actualState)
- verifyAll {
- mockedSplitTunneling.enabledChange = any()
- mockedSplitTunneling.excludedAppsChange = any()
- }
}
}
@Test
fun `include app should work`() = runTest {
- var excludedAppsCallback = slot<(Set<String>) -> Unit>()
val app = AppData("test", 0, "testName")
- every { mockedSplitTunneling.includeApp(app.packageName) } just runs
- every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers
- {
- excludedAppsCallback = lambda()
- excludedAppsCallback.invoke(setOf(app.packageName))
- }
- every { mockedSplitTunneling.enabledChange = captureLambda() } answers
- {
- lambda<(Boolean) -> Unit>().invoke(true)
- }
initTestSubject(listOf(app))
+ excludedApps.value = setOf(AppId(app.packageName))
val expectedStateBeforeAction =
SplitTunnelingUiState.ShowAppList(
@@ -149,35 +118,22 @@ class SplitTunnelingViewModelTest {
includedApps = listOf(app),
showSystemApps = false
)
+ coEvery { mockedSplitTunnelingRepository.includeApp(AppId(app.packageName)) } returns
+ Unit.right()
testSubject.uiState.test {
assertEquals(expectedStateBeforeAction, awaitItem())
testSubject.onIncludeAppClick(app.packageName)
- excludedAppsCallback.invoke(emptySet())
+ excludedApps.value = emptySet()
assertEquals(expectedStateAfterAction, awaitItem())
- verifyAll {
- mockedSplitTunneling.enabledChange = any()
- mockedSplitTunneling.excludedAppsChange = any()
- mockedSplitTunneling.includeApp(app.packageName)
- }
+ coVerify { mockedSplitTunnelingRepository.includeApp(AppId(app.packageName)) }
}
}
@Test
fun `onExcludeApp should result in new uiState with app excluded`() = runTest {
- var excludedAppsCallback = slot<(Set<String>) -> Unit>()
val app = AppData("test", 0, "testName")
- every { mockedSplitTunneling.excludeApp(app.packageName) } just runs
- every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers
- {
- excludedAppsCallback = lambda()
- excludedAppsCallback.invoke(emptySet())
- }
- every { mockedSplitTunneling.enabledChange = captureLambda() } answers
- {
- lambda<(Boolean) -> Unit>().invoke(true)
- }
initTestSubject(listOf(app))
@@ -197,32 +153,23 @@ class SplitTunnelingViewModelTest {
showSystemApps = false
)
+ coEvery { mockedSplitTunnelingRepository.excludeApp(AppId(app.packageName)) } returns
+ Unit.right()
+
testSubject.uiState.test {
assertEquals(expectedStateBeforeAction, awaitItem())
testSubject.onExcludeAppClick(app.packageName)
- excludedAppsCallback.invoke(setOf(app.packageName))
+ excludedApps.value = setOf(AppId(app.packageName))
assertEquals(expectedStateAfterAction, awaitItem())
- verifyAll {
- mockedSplitTunneling.enabledChange = any()
- mockedSplitTunneling.excludedAppsChange = any()
- mockedSplitTunneling.excludeApp(app.packageName)
- }
+ coVerify { mockedSplitTunnelingRepository.excludeApp(AppId(app.packageName)) }
}
}
@Test
fun `when split tunneling is disabled uiState should be disabled`() = runTest {
- every { mockedSplitTunneling.excludedAppsChange = captureLambda() } answers
- {
- lambda<(Set<String>) -> Unit>().invoke(emptySet())
- }
- every { mockedSplitTunneling.enabledChange = captureLambda() } answers
- {
- lambda<(Boolean) -> Unit>().invoke(false)
- }
-
initTestSubject(emptyList())
+ enabled.value = false
val expectedState = SplitTunnelingUiState.ShowAppList(enabled = false)
@@ -234,15 +181,10 @@ class SplitTunnelingViewModelTest {
private fun initTestSubject(appList: List<AppData>) {
every { mockedApplicationsProvider.getAppsList() } returns appList
- every { mockedServiceConnectionManager.connectionState } returns
- MutableStateFlow(
- ServiceConnectionState.ConnectedReady(mockedServiceConnectionContainer)
- )
- every { mockedServiceConnectionContainer.splitTunneling } returns mockedSplitTunneling
testSubject =
SplitTunnelingViewModel(
mockedApplicationsProvider,
- mockedServiceConnectionManager,
+ mockedSplitTunnelingRepository,
UnconfinedTestDispatcher()
)
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
index 6934384643..ef3b34effc 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VoucherDialogViewModelTest.kt
@@ -1,7 +1,8 @@
package net.mullvad.mullvadvpn.viewmodel
-import android.content.res.Resources
import app.cash.turbine.test
+import arrow.core.left
+import arrow.core.right
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
@@ -10,18 +11,13 @@ import io.mockk.unmockkAll
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertTrue
-import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.VoucherDialogState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.VoucherSubmission
-import net.mullvad.mullvadvpn.model.VoucherSubmissionError
-import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
-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.ui.serviceconnection.VoucherRedeemer
-import net.mullvad.mullvadvpn.ui.serviceconnection.voucherRedeemer
+import net.mullvad.mullvadvpn.lib.model.RedeemVoucherError
+import net.mullvad.mullvadvpn.lib.model.RedeemVoucherSuccess
+import net.mullvad.mullvadvpn.lib.shared.VoucherRepository
+import org.joda.time.DateTime
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
@@ -30,26 +26,15 @@ import org.junit.jupiter.api.extension.ExtendWith
@ExtendWith(TestCoroutineRule::class)
class VoucherDialogViewModelTest {
- private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
- private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
- private val mockVoucherSubmission: VoucherSubmission = mockk()
- private val serviceConnectionState =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
+ private val mockVoucherSubmission: RedeemVoucherSuccess = mockk()
- private val mockVoucherRedeemer: VoucherRedeemer = mockk()
- private val mockResources: Resources = mockk()
+ private val mockVoucherRepository: VoucherRepository = mockk()
private lateinit var viewModel: VoucherDialogViewModel
@BeforeEach
fun setup() {
- every { mockServiceConnectionManager.connectionState } returns serviceConnectionState
-
- viewModel =
- VoucherDialogViewModel(
- serviceConnectionManager = mockServiceConnectionManager,
- resources = mockResources
- )
+ viewModel = VoucherDialogViewModel(voucherRepository = mockVoucherRepository)
}
@AfterEach
@@ -62,36 +47,31 @@ class VoucherDialogViewModelTest {
val voucher = DUMMY_INVALID_VOUCHER
// Arrange
- every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
- every { mockVoucherSubmission.timeAdded } returns 0
- coEvery { mockVoucherRedeemer.submit(voucher) } returns
- VoucherSubmissionResult.Ok(mockVoucherSubmission)
+ val timeAdded = 0L
+ val newExpiry = DateTime()
+ coEvery { mockVoucherRepository.submitVoucher(voucher) } returns
+ RedeemVoucherSuccess(timeAdded, newExpiry).right()
// Act
assertIs<VoucherDialogState.Default>(viewModel.uiState.value.voucherState)
viewModel.onRedeem(voucher)
// Assert
- coVerify(exactly = 1) { mockVoucherRedeemer.submit(voucher) }
+ coVerify(exactly = 1) { mockVoucherRepository.submitVoucher(voucher) }
}
@Test
fun `given invalid voucher when redeeming then show error`() = runTest {
val voucher = DUMMY_INVALID_VOUCHER
- val dummyStringResource = DUMMY_STRING_RESOURCE
// Arrange
- every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
- every { mockResources.getString(any()) } returns dummyStringResource
every { mockVoucherSubmission.timeAdded } returns 0
- coEvery { mockVoucherRedeemer.submit(voucher) } returns
- VoucherSubmissionResult.Error(VoucherSubmissionError.OtherError)
+ coEvery { mockVoucherRepository.submitVoucher(voucher) } returns
+ RedeemVoucherError.InvalidVoucher.left()
// Act, Assert
viewModel.uiState.test {
assertEquals(viewModel.uiState.value, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
viewModel.onRedeem(voucher)
assertTrue { awaitItem().voucherState is VoucherDialogState.Verifying }
assertTrue { awaitItem().voucherState is VoucherDialogState.Error }
@@ -101,20 +81,15 @@ class VoucherDialogViewModelTest {
@Test
fun `given valid voucher when redeeming then show success`() = runTest {
val voucher = DUMMY_VALID_VOUCHER
- val dummyStringResource = DUMMY_STRING_RESOURCE
// Arrange
- every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
- every { mockResources.getString(any()) } returns dummyStringResource
every { mockVoucherSubmission.timeAdded } returns 0
- coEvery { mockVoucherRedeemer.submit(voucher) } returns
- VoucherSubmissionResult.Ok(VoucherSubmission(0, DUMMY_STRING_RESOURCE))
+ coEvery { mockVoucherRepository.submitVoucher(voucher) } returns
+ RedeemVoucherSuccess(0, DateTime()).right()
// Act, Assert
viewModel.uiState.test {
assertEquals(viewModel.uiState.value, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
viewModel.onRedeem(voucher)
assertTrue { awaitItem().voucherState is VoucherDialogState.Verifying }
assertTrue { awaitItem().voucherState is VoucherDialogState.Success }
@@ -124,20 +99,15 @@ class VoucherDialogViewModelTest {
@Test
fun `when voucher input is changed then clear error`() = runTest {
val voucher = DUMMY_INVALID_VOUCHER
- val dummyStringResource = DUMMY_STRING_RESOURCE
// Arrange
- every { mockServiceConnectionManager.voucherRedeemer() } returns mockVoucherRedeemer
- every { mockResources.getString(any()) } returns dummyStringResource
every { mockVoucherSubmission.timeAdded } returns 0
- coEvery { mockVoucherRedeemer.submit(voucher) } returns
- VoucherSubmissionResult.Error(VoucherSubmissionError.OtherError)
+ coEvery { mockVoucherRepository.submitVoucher(voucher) } returns
+ RedeemVoucherError.VoucherAlreadyUsed.left()
// Act, Assert
viewModel.uiState.test {
assertEquals(viewModel.uiState.value, awaitItem())
- serviceConnectionState.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
viewModel.onRedeem(voucher)
assertTrue { awaitItem().voucherState is VoucherDialogState.Verifying }
assertTrue { awaitItem().voucherState is VoucherDialogState.Error }
@@ -149,6 +119,5 @@ class VoucherDialogViewModelTest {
companion object {
private const val DUMMY_VALID_VOUCHER = "dummy_valid_voucher"
private const val DUMMY_INVALID_VOUCHER = "dummy_invalid_voucher"
- private const val DUMMY_STRING_RESOURCE = "dummy_string_resource"
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
index 11992c40c0..29a6c764ba 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt
@@ -1,12 +1,13 @@
package net.mullvad.mullvadvpn.viewmodel
-import android.content.res.Resources
import androidx.lifecycle.viewModelScope
import app.cash.turbine.test
+import arrow.core.right
+import io.mockk.coEvery
+import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.unmockkAll
-import io.mockk.verify
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlinx.coroutines.cancel
@@ -14,19 +15,19 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
-import net.mullvad.mullvadvpn.model.Constraint
-import net.mullvad.mullvadvpn.model.Port
-import net.mullvad.mullvadvpn.model.PortRange
-import net.mullvad.mullvadvpn.model.QuantumResistantState
-import net.mullvad.mullvadvpn.model.RelayConstraints
-import net.mullvad.mullvadvpn.model.RelaySettings
-import net.mullvad.mullvadvpn.model.Settings
-import net.mullvad.mullvadvpn.model.TunnelOptions
-import net.mullvad.mullvadvpn.model.WireguardConstraints
-import net.mullvad.mullvadvpn.model.WireguardTunnelOptions
+import net.mullvad.mullvadvpn.lib.model.Constraint
+import net.mullvad.mullvadvpn.lib.model.Mtu
+import net.mullvad.mullvadvpn.lib.model.Port
+import net.mullvad.mullvadvpn.lib.model.PortRange
+import net.mullvad.mullvadvpn.lib.model.QuantumResistantState
+import net.mullvad.mullvadvpn.lib.model.RelayConstraints
+import net.mullvad.mullvadvpn.lib.model.RelaySettings
+import net.mullvad.mullvadvpn.lib.model.Settings
+import net.mullvad.mullvadvpn.lib.model.TunnelOptions
+import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
+import net.mullvad.mullvadvpn.lib.model.WireguardTunnelOptions
+import net.mullvad.mullvadvpn.repository.RelayListRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
-import net.mullvad.mullvadvpn.usecase.PortRangeUseCase
-import net.mullvad.mullvadvpn.usecase.RelayListUseCase
import net.mullvad.mullvadvpn.usecase.SystemVpnSettingsUseCase
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@@ -37,10 +38,8 @@ import org.junit.jupiter.api.extension.ExtendWith
class VpnSettingsViewModelTest {
private val mockSettingsRepository: SettingsRepository = mockk()
- private val mockResources: Resources = mockk()
- private val mockPortRangeUseCase: PortRangeUseCase = mockk()
- private val mockRelayListUseCase: RelayListUseCase = mockk()
private val mockSystemVpnSettingsUseCase: SystemVpnSettingsUseCase = mockk(relaxed = true)
+ private val mockRelayListRepository: RelayListRepository = mockk()
private val mockSettingsUpdate = MutableStateFlow<Settings?>(null)
private val portRangeFlow = MutableStateFlow(emptyList<PortRange>())
@@ -50,15 +49,13 @@ class VpnSettingsViewModelTest {
@BeforeEach
fun setup() {
every { mockSettingsRepository.settingsUpdates } returns mockSettingsUpdate
- every { mockPortRangeUseCase.portRanges() } returns portRangeFlow
+ every { mockRelayListRepository.portRanges } returns portRangeFlow
viewModel =
VpnSettingsViewModel(
repository = mockSettingsRepository,
- resources = mockResources,
- portRangeUseCase = mockPortRangeUseCase,
- relayListUseCase = mockRelayListUseCase,
systemVpnSettingsUseCase = mockSystemVpnSettingsUseCase,
+ relayListRepository = mockRelayListRepository,
dispatcher = UnconfinedTestDispatcher()
)
}
@@ -73,11 +70,11 @@ class VpnSettingsViewModelTest {
fun `onSelectQuantumResistanceSetting should invoke setWireguardQuantumResistant on SettingsRepository`() =
runTest {
val quantumResistantState = QuantumResistantState.On
- every {
+ coEvery {
mockSettingsRepository.setWireguardQuantumResistant(quantumResistantState)
- } returns Unit
+ } returns Unit.right()
viewModel.onSelectQuantumResistanceSetting(quantumResistantState)
- verify(exactly = 1) {
+ coVerify(exactly = 1) {
mockSettingsRepository.setWireguardQuantumResistant(quantumResistantState)
}
}
@@ -105,7 +102,8 @@ class VpnSettingsViewModelTest {
every { mockSettings.tunnelOptions } returns mockTunnelOptions
every { mockTunnelOptions.wireguard } returns mockWireguardTunnelOptions
every { mockWireguardTunnelOptions.quantumResistant } returns expectedResistantState
- every { mockSettings.relaySettings } returns mockk<RelaySettings.Normal>(relaxed = true)
+ every { mockWireguardTunnelOptions.mtu } returns Mtu(0)
+ every { mockSettings.relaySettings } returns mockk<RelaySettings>(relaxed = true)
viewModel.uiState.test {
assertEquals(defaultResistantState, awaitItem().quantumResistant)
@@ -120,7 +118,7 @@ class VpnSettingsViewModelTest {
// Arrange
val expectedPort: Constraint<Port> = Constraint.Only(Port(99))
val mockSettings: Settings = mockk(relaxed = true)
- val mockRelaySettings: RelaySettings.Normal = mockk()
+ val mockRelaySettings: RelaySettings = mockk()
val mockRelayConstraints: RelayConstraints = mockk()
val mockWireguardConstraints: WireguardConstraints = mockk()
@@ -128,10 +126,19 @@ class VpnSettingsViewModelTest {
every { mockRelaySettings.relayConstraints } returns mockRelayConstraints
every { mockRelayConstraints.wireguardConstraints } returns mockWireguardConstraints
every { mockWireguardConstraints.port } returns expectedPort
+ every { mockSettings.tunnelOptions } returns
+ TunnelOptions(
+ wireguard =
+ WireguardTunnelOptions(
+ mtu = null,
+ quantumResistant = QuantumResistantState.Off
+ ),
+ dnsOptions = mockk(relaxed = true)
+ )
// Act, Assert
viewModel.uiState.test {
- assertIs<Constraint.Any<Port>>(awaitItem().selectedWireguardPort)
+ assertIs<Constraint.Any>(awaitItem().selectedWireguardPort)
mockSettingsUpdate.value = mockSettings
assertEquals(expectedPort, awaitItem().customWireguardPort)
assertEquals(expectedPort, awaitItem().selectedWireguardPort)
@@ -144,14 +151,15 @@ class VpnSettingsViewModelTest {
// Arrange
val wireguardPort: Constraint<Port> = Constraint.Only(Port(99))
val wireguardConstraints = WireguardConstraints(port = wireguardPort)
- every { mockRelayListUseCase.updateSelectedWireguardConstraints(any()) } returns Unit
+ coEvery { mockRelayListRepository.updateSelectedWireguardConstraints(any()) } returns
+ Unit.right()
// Act
viewModel.onWireguardPortSelected(wireguardPort)
// Assert
- verify(exactly = 1) {
- mockRelayListUseCase.updateSelectedWireguardConstraints(wireguardConstraints)
+ coVerify(exactly = 1) {
+ mockRelayListRepository.updateSelectedWireguardConstraints(wireguardConstraints)
}
}
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt
index 91554193bc..3113450276 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModelTest.kt
@@ -13,27 +13,23 @@ import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import net.mullvad.mullvadvpn.compose.state.PaymentState
-import net.mullvad.mullvadvpn.compose.state.WelcomeUiState
import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule
import net.mullvad.mullvadvpn.lib.common.test.assertLists
+import net.mullvad.mullvadvpn.lib.model.AccountData
+import net.mullvad.mullvadvpn.lib.model.AccountToken
+import net.mullvad.mullvadvpn.lib.model.Device
+import net.mullvad.mullvadvpn.lib.model.DeviceState
+import net.mullvad.mullvadvpn.lib.model.TunnelState
+import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.payment.model.PaymentAvailability
import net.mullvad.mullvadvpn.lib.payment.model.PaymentProduct
import net.mullvad.mullvadvpn.lib.payment.model.PurchaseResult
-import net.mullvad.mullvadvpn.model.AccountAndDevice
-import net.mullvad.mullvadvpn.model.AccountExpiry
-import net.mullvad.mullvadvpn.model.Device
-import net.mullvad.mullvadvpn.model.DeviceState
-import net.mullvad.mullvadvpn.model.TunnelState
-import net.mullvad.mullvadvpn.repository.AccountRepository
-import net.mullvad.mullvadvpn.repository.DeviceRepository
-import net.mullvad.mullvadvpn.ui.serviceconnection.AuthTokenCache
-import net.mullvad.mullvadvpn.ui.serviceconnection.ConnectionProxy
-import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionContainer
+import net.mullvad.mullvadvpn.lib.shared.AccountRepository
+import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
+import net.mullvad.mullvadvpn.lib.shared.DeviceRepository
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionState
-import net.mullvad.mullvadvpn.ui.serviceconnection.authTokenCache
import net.mullvad.mullvadvpn.usecase.PaymentUseCase
-import net.mullvad.talpid.util.EventNotifier
import org.joda.time.DateTime
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
@@ -44,21 +40,20 @@ import org.junit.jupiter.api.extension.ExtendWith
class WelcomeViewModelTest {
private val serviceConnectionStateFlow =
- MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Disconnected)
- private val deviceStateFlow = MutableStateFlow<DeviceState>(DeviceState.Initial)
- private val accountExpiryStateFlow = MutableStateFlow<AccountExpiry>(AccountExpiry.Missing)
+ MutableStateFlow<ServiceConnectionState>(ServiceConnectionState.Unbound)
+ private val deviceStateFlow = MutableStateFlow<DeviceState?>(DeviceState.LoggedOut)
+ private val accountExpiryStateFlow = MutableStateFlow<AccountData?>(null)
private val purchaseResultFlow = MutableStateFlow<PurchaseResult?>(null)
private val paymentAvailabilityFlow = MutableStateFlow<PaymentAvailability?>(null)
- // Service connections
- private val mockServiceConnectionContainer: ServiceConnectionContainer = mockk()
+ // ConnectionProxy
private val mockConnectionProxy: ConnectionProxy = mockk()
// Event notifiers
- private val eventNotifierTunnelUiState = EventNotifier<TunnelState>(TunnelState.Disconnected())
+ private val tunnelState = MutableStateFlow<TunnelState>(TunnelState.Disconnected())
private val mockAccountRepository: AccountRepository = mockk(relaxed = true)
- private val mockDeviceRepository: DeviceRepository = mockk()
+ private val mockDeviceRepository: DeviceRepository = mockk(relaxed = true)
private val mockServiceConnectionManager: ServiceConnectionManager = mockk()
private val mockPaymentUseCase: PaymentUseCase = mockk(relaxed = true)
@@ -66,18 +61,15 @@ class WelcomeViewModelTest {
@BeforeEach
fun setup() {
- mockkStatic(SERVICE_CONNECTION_MANAGER_EXTENSIONS)
mockkStatic(PURCHASE_RESULT_EXTENSIONS_CLASS)
every { mockDeviceRepository.deviceState } returns deviceStateFlow
every { mockServiceConnectionManager.connectionState } returns serviceConnectionStateFlow
- every { mockServiceConnectionContainer.connectionProxy } returns mockConnectionProxy
+ every { mockConnectionProxy.tunnelState } returns tunnelState
- every { mockConnectionProxy.onUiStateChange } returns eventNotifierTunnelUiState
-
- every { mockAccountRepository.accountExpiryState } returns accountExpiryStateFlow
+ every { mockAccountRepository.accountData } returns accountExpiryStateFlow
coEvery { mockPaymentUseCase.purchaseResult } returns purchaseResultFlow
@@ -87,8 +79,8 @@ class WelcomeViewModelTest {
WelcomeViewModel(
accountRepository = mockAccountRepository,
deviceRepository = mockDeviceRepository,
- serviceConnectionManager = mockServiceConnectionManager,
paymentUseCase = mockPaymentUseCase,
+ connectionProxy = mockConnectionProxy,
pollAccountExpiry = false,
isPlayBuild = false
)
@@ -103,10 +95,8 @@ class WelcomeViewModelTest {
@Test
fun `on onSitePaymentClick call uiSideEffect should emit OpenAccountView`() = runTest {
// Arrange
- val mockToken = "4444 5555 6666 7777"
- val mockAuthTokenCache: AuthTokenCache = mockk(relaxed = true)
- every { mockServiceConnectionManager.authTokenCache() } returns mockAuthTokenCache
- coEvery { mockAuthTokenCache.fetchAuthToken() } returns mockToken
+ val mockToken = WebsiteAuthToken.fromString("154c4cc94810fddac78398662b7fa0c7")
+ coEvery { mockAccountRepository.getWebsiteAuthToken() } returns mockToken
// Act, Assert
viewModel.uiSideEffect.test {
@@ -124,10 +114,9 @@ class WelcomeViewModelTest {
// Act, Assert
viewModel.uiState.test {
- assertEquals(WelcomeUiState(), awaitItem())
- eventNotifierTunnelUiState.notify(tunnelUiStateTestItem)
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ // Default state
+ awaitItem()
+ tunnelState.emit(tunnelUiStateTestItem)
val result = awaitItem()
assertEquals(tunnelUiStateTestItem, result.tunnelState)
}
@@ -137,21 +126,17 @@ class WelcomeViewModelTest {
fun `when DeviceRepository returns LoggedIn uiState should include new accountNumber`() =
runTest {
// Arrange
- val expectedAccountNumber = "4444555566667777"
+ val expectedAccountNumber = AccountToken("4444555566667777")
val device: Device = mockk()
every { device.displayName() } returns ""
// Act, Assert
viewModel.uiState.test {
- assertEquals(WelcomeUiState(), awaitItem())
+ // Default state
+ awaitItem()
paymentAvailabilityFlow.value = null
deviceStateFlow.value =
- DeviceState.LoggedIn(
- accountAndDevice =
- AccountAndDevice(account_token = expectedAccountNumber, device = device)
- )
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
+ DeviceState.LoggedIn(accountToken = expectedAccountNumber, device = device)
assertEquals(expectedAccountNumber, awaitItem().accountNumber)
}
}
@@ -159,7 +144,7 @@ class WelcomeViewModelTest {
@Test
fun `when user has added time then uiSideEffect should emit OpenConnectScreen`() = runTest {
// Arrange
- accountExpiryStateFlow.emit(AccountExpiry.Available(DateTime().plusDays(1)))
+ accountExpiryStateFlow.emit(AccountData(mockk(relaxed = true), DateTime().plusDays(1)))
// Act, Assert
viewModel.uiSideEffect.test {
@@ -179,8 +164,6 @@ class WelcomeViewModelTest {
// Default item
awaitItem()
paymentAvailabilityFlow.tryEmit(productsUnavailable)
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
val result = awaitItem().billingPaymentState
assertIs<PaymentState.NoPayment>(result)
}
@@ -192,8 +175,6 @@ class WelcomeViewModelTest {
// Arrange
val paymentOtherError = PaymentAvailability.Error.Other(mockk())
paymentAvailabilityFlow.tryEmit(paymentOtherError)
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -207,8 +188,6 @@ class WelcomeViewModelTest {
runTest { // Arrange
val paymentBillingError = PaymentAvailability.Error.BillingUnavailable
paymentAvailabilityFlow.value = paymentBillingError
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -225,8 +204,6 @@ class WelcomeViewModelTest {
val expectedProductList = listOf(mockProduct)
val productsAvailable = PaymentAvailability.ProductsAvailable(listOf(mockProduct))
paymentAvailabilityFlow.value = productsAvailable
- serviceConnectionStateFlow.value =
- ServiceConnectionState.ConnectedReady(mockServiceConnectionContainer)
// Act, Assert
viewModel.uiState.test {
@@ -237,8 +214,6 @@ class WelcomeViewModelTest {
}
companion object {
- private const val SERVICE_CONNECTION_MANAGER_EXTENSIONS =
- "net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManagerExtensionsKt"
private const val PURCHASE_RESULT_EXTENSIONS_CLASS =
"net.mullvad.mullvadvpn.util.PurchaseResultExtensionsKt"
}