diff options
| author | David Göransson <david.goransson@mullvad.net> | 2024-05-29 17:18:29 +0200 |
|---|---|---|
| committer | David Göransson <david.goransson@mullvad.net> | 2024-05-29 17:18:29 +0200 |
| commit | ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8 (patch) | |
| tree | 9d085bc81caed9409e3a4360490c06c2da4fbba8 /android/app/src/test | |
| parent | 8e14a8d4287af66a57a98db79d3ac320c2dad4a1 (diff) | |
| parent | 767b97eda756f4ec4e67fb5fa2ae664277291e8f (diff) | |
| download | mullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.tar.xz mullvadvpn-ad90145a5d86d8c1e6e70f2238f11edf5e50f8d8.zip | |
Merge branch 'android-grpc'
Diffstat (limited to 'android/app/src/test')
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" } |
