diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2024-06-13 16:38:16 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2024-06-14 13:27:05 +0200 |
| commit | 4e2c10994a7bd7916e74aedbe8f90e0da67d1a42 (patch) | |
| tree | 17f829e79067266675dce15e39b63b06b6e619b2 /android/app | |
| parent | 7dd2345f65acb9fe9a9b57809603db3a65417a8b (diff) | |
| download | mullvadvpn-4e2c10994a7bd7916e74aedbe8f90e0da67d1a42.tar.xz mullvadvpn-4e2c10994a7bd7916e74aedbe8f90e0da67d1a42.zip | |
Add unit tests for Api access method
Diffstat (limited to 'android/app')
7 files changed, 751 insertions, 5 deletions
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/data/DummyUUID.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/data/DummyUUID.kt new file mode 100644 index 0000000000..3454af685c --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/data/DummyUUID.kt @@ -0,0 +1,3 @@ +package net.mullvad.mullvadvpn.data + +const val UUID = "12345678-1234-5678-1234-567812345678" diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepositoryTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepositoryTest.kt new file mode 100644 index 0000000000..cb1042e6f8 --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepositoryTest.kt @@ -0,0 +1,280 @@ +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 kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.data.UUID +import net.mullvad.mullvadvpn.lib.daemon.grpc.ManagementService +import net.mullvad.mullvadvpn.lib.model.AddApiAccessMethodError +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodName +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting +import net.mullvad.mullvadvpn.lib.model.GetApiAccessMethodError +import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting +import net.mullvad.mullvadvpn.lib.model.SetApiAccessMethodError +import net.mullvad.mullvadvpn.lib.model.Settings +import net.mullvad.mullvadvpn.lib.model.TestApiAccessMethodError +import net.mullvad.mullvadvpn.lib.model.UnknownApiAccessMethodError +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class ApiAccessRepositoryTest { + private val mockManagementService: ManagementService = mockk() + + private lateinit var apiAccessRepository: ApiAccessRepository + + private val settingsFlow: MutableStateFlow<Settings> = MutableStateFlow(mockk(relaxed = true)) + + @BeforeEach + fun setUp() { + every { mockManagementService.settings } returns settingsFlow + every { mockManagementService.currentAccessMethod } returns emptyFlow() + + apiAccessRepository = + ApiAccessRepository( + managementService = mockManagementService, + dispatcher = UnconfinedTestDispatcher() + ) + } + + @Test + fun `adding api access method should return id when successful`() = runTest { + // Arrange + val newAccessMethodSetting: NewAccessMethodSetting = mockk() + val accessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + coEvery { mockManagementService.addApiAccessMethod(newAccessMethodSetting) } returns + accessMethodId.right() + + // Act + val result = apiAccessRepository.addApiAccessMethod(newAccessMethodSetting) + + // Assert + coVerify { mockManagementService.addApiAccessMethod(newAccessMethodSetting) } + assertEquals(accessMethodId.right(), result) + } + + @Test + fun `adding api access method should return error when not successful`() = runTest { + // Arrange + val newAccessMethodSetting: NewAccessMethodSetting = mockk() + val addApiAccessMethodError: AddApiAccessMethodError.Unknown = mockk() + coEvery { mockManagementService.addApiAccessMethod(newAccessMethodSetting) } returns + addApiAccessMethodError.left() + + // Act + val result = apiAccessRepository.addApiAccessMethod(newAccessMethodSetting) + + // Assert + coVerify { mockManagementService.addApiAccessMethod(newAccessMethodSetting) } + assertEquals(addApiAccessMethodError.left(), result) + } + + @Test + fun `setting api access method should return successful when successful`() = runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + coEvery { mockManagementService.setApiAccessMethod(apiAccessMethodId) } returns Unit.right() + + // Act + val result = apiAccessRepository.setCurrentApiAccessMethod(apiAccessMethodId) + + // Assert + coVerify { mockManagementService.setApiAccessMethod(apiAccessMethodId) } + assertEquals(Unit.right(), result) + } + + @Test + fun `setting api access method should return error when not successful`() = runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val setApiAccessMethodError: SetApiAccessMethodError = mockk() + coEvery { mockManagementService.setApiAccessMethod(apiAccessMethodId) } returns + setApiAccessMethodError.left() + + // Act + val result = apiAccessRepository.setCurrentApiAccessMethod(apiAccessMethodId) + + // Assert + coVerify { mockManagementService.setApiAccessMethod(apiAccessMethodId) } + assertEquals(setApiAccessMethodError.left(), result) + } + + @Test + fun `test api access method by id should return successful when successful`() = runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + coEvery { mockManagementService.testApiAccessMethodById(apiAccessMethodId) } returns + Unit.right() + + // Act + val result = apiAccessRepository.testApiAccessMethodById(apiAccessMethodId) + + // Assert + coVerify { mockManagementService.testApiAccessMethodById(apiAccessMethodId) } + assertEquals(Unit.right(), result) + } + + @Test + fun `test api access method by id should return error when not successful`() = runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val testApiAccessMethodError: TestApiAccessMethodError = mockk() + coEvery { mockManagementService.testApiAccessMethodById(apiAccessMethodId) } returns + testApiAccessMethodError.left() + + // Act + val result = apiAccessRepository.testApiAccessMethodById(apiAccessMethodId) + + // Assert + coVerify { mockManagementService.testApiAccessMethodById(apiAccessMethodId) } + assertEquals(testApiAccessMethodError.left(), result) + } + + @Test + fun `test custom api access method should return successful when successful`() = runTest { + // Arrange + val customProxy: ApiAccessMethod.CustomProxy = mockk() + coEvery { mockManagementService.testCustomApiAccessMethod(customProxy) } returns + Unit.right() + + // Act + val result = apiAccessRepository.testCustomApiAccessMethod(customProxy) + + // Assert + coVerify { mockManagementService.testCustomApiAccessMethod(customProxy) } + assertEquals(Unit.right(), result) + } + + @Test + fun `test custom api access method should return error when not successful`() = runTest { + // Arrange + val customProxy: ApiAccessMethod.CustomProxy = mockk() + val testApiAccessMethodError: TestApiAccessMethodError = mockk() + coEvery { mockManagementService.testCustomApiAccessMethod(customProxy) } returns + testApiAccessMethodError.left() + + // Act + val result = apiAccessRepository.testCustomApiAccessMethod(customProxy) + + // Assert + coVerify { mockManagementService.testCustomApiAccessMethod(customProxy) } + assertEquals(testApiAccessMethodError.left(), result) + } + + @Test + fun `get access method by id should return access method when id matches in settings`() = + runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val expectedResult = + ApiAccessMethodSetting( + name = ApiAccessMethodName.fromString("Name"), + apiAccessMethod = ApiAccessMethod.Direct, + enabled = true, + id = apiAccessMethodId + ) + val mockSettings: Settings = mockk() + every { mockSettings.apiAccessMethodSettings } returns listOf(expectedResult) + settingsFlow.value = mockSettings + + // Act + val result = apiAccessRepository.getApiAccessMethodSettingById(apiAccessMethodId) + + // Assert + assertEquals(expectedResult.right(), result) + } + + @Test + fun `get access method by id should return not found error when id does not matches in settings`() = + runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val expectedError = GetApiAccessMethodError.NotFound + val mockSettings: Settings = mockk() + every { mockSettings.apiAccessMethodSettings } returns emptyList() + settingsFlow.value = mockSettings + + // Act + val result = apiAccessRepository.getApiAccessMethodSettingById(apiAccessMethodId) + + // Assert + assertEquals(expectedError.left(), result) + } + + @Test + fun `when setting enable for api access method should return successful when successful`() = + runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val apiAccessMethodSetting = + ApiAccessMethodSetting( + name = ApiAccessMethodName.fromString("Name"), + apiAccessMethod = ApiAccessMethod.Direct, + enabled = true, + id = apiAccessMethodId + ) + val mockSettings: Settings = mockk() + every { mockSettings.apiAccessMethodSettings } returns listOf(apiAccessMethodSetting) + coEvery { mockManagementService.updateApiAccessMethod(apiAccessMethodSetting) } returns + Unit.right() + settingsFlow.value = mockSettings + + // Act + val result = apiAccessRepository.setEnabledApiAccessMethod(apiAccessMethodId, true) + + // Assert + assertEquals(Unit.right(), result) + } + + @Test + fun `when setting enable for api access method should return error when not method not found`() = + runTest { + // Arrange + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val expectedError = GetApiAccessMethodError.NotFound + val mockSettings: Settings = mockk() + every { mockSettings.apiAccessMethodSettings } returns emptyList() + settingsFlow.value = mockSettings + + // Act + val result = apiAccessRepository.setEnabledApiAccessMethod(apiAccessMethodId, true) + + // Assert + assertEquals(expectedError.left(), result) + } + + @Test + fun `when setting enable for api access method should return error when not successful`() = + runTest { + // Arrange + val expectedError: UnknownApiAccessMethodError = mockk() + val apiAccessMethodId: ApiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val apiAccessMethodSetting = + ApiAccessMethodSetting( + name = ApiAccessMethodName.fromString("Name"), + apiAccessMethod = ApiAccessMethod.Direct, + enabled = true, + id = apiAccessMethodId + ) + val mockSettings: Settings = mockk() + every { mockSettings.apiAccessMethodSettings } returns listOf(apiAccessMethodSetting) + coEvery { mockManagementService.updateApiAccessMethod(apiAccessMethodSetting) } returns + expectedError.left() + settingsFlow.value = mockSettings + + // Act + val result = apiAccessRepository.setEnabledApiAccessMethod(apiAccessMethodId, true) + + // Assert + assertEquals(expectedError.left(), result) + } +} 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 b139853471..95df8dc359 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 @@ -9,6 +9,7 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.data.UUID import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.model.AccountNumber import net.mullvad.mullvadvpn.lib.model.Device @@ -86,8 +87,4 @@ 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/viewmodel/AccountViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt index 76c176c519..706d8031e7 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 @@ -13,6 +13,7 @@ import kotlin.test.assertIs import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import net.mullvad.mullvadvpn.compose.state.PaymentState +import net.mullvad.mullvadvpn.data.UUID import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule import net.mullvad.mullvadvpn.lib.common.test.assertLists import net.mullvad.mullvadvpn.lib.model.AccountNumber @@ -214,6 +215,5 @@ class AccountViewModelTest { 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/ApiAccessMethodDetailsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ApiAccessMethodDetailsViewModelTest.kt new file mode 100644 index 0000000000..631deb12e4 --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ApiAccessMethodDetailsViewModelTest.kt @@ -0,0 +1,180 @@ +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.coVerify +import io.mockk.every +import io.mockk.mockk +import java.time.Duration +import kotlin.test.assertIs +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.time.delay +import net.mullvad.mullvadvpn.compose.state.ApiAccessMethodDetailsUiState +import net.mullvad.mullvadvpn.data.UUID +import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting +import net.mullvad.mullvadvpn.lib.model.TestApiAccessMethodError +import net.mullvad.mullvadvpn.lib.model.UnknownApiAccessMethodError +import net.mullvad.mullvadvpn.repository.ApiAccessRepository +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(TestCoroutineRule::class) +class ApiAccessMethodDetailsViewModelTest { + private val mockApiAccessRepository: ApiAccessRepository = mockk() + private val apiAccessMethodId = ApiAccessMethodId.fromString(UUID) + + private lateinit var apiAccessMethodDetailsViewModel: ApiAccessMethodDetailsViewModel + + private val accessMethodFlow = MutableStateFlow<ApiAccessMethodSetting>(mockk(relaxed = true)) + private val enabledMethodsFlow = MutableStateFlow<List<ApiAccessMethodSetting>>(emptyList()) + private val currentAccessMethodFlow = MutableStateFlow<ApiAccessMethodSetting?>(null) + + @BeforeEach + fun setUp() { + every { mockApiAccessRepository.apiAccessMethodSettingById(apiAccessMethodId) } returns + accessMethodFlow + every { mockApiAccessRepository.enabledApiAccessMethods() } returns enabledMethodsFlow + every { mockApiAccessRepository.currentAccessMethod } returns currentAccessMethodFlow + + apiAccessMethodDetailsViewModel = + ApiAccessMethodDetailsViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessRepository = mockApiAccessRepository + ) + } + + @Test + fun `when calling set current method and testing is successful should call set method`() = + runTest { + // Arrange + coEvery { mockApiAccessRepository.testApiAccessMethodById(apiAccessMethodId) } returns + Unit.right() + coEvery { mockApiAccessRepository.setCurrentApiAccessMethod(any()) } returns + Unit.right() + + // Act + apiAccessMethodDetailsViewModel.setCurrentMethod() + + // Assert + coVerify(exactly = 1) { + mockApiAccessRepository.setCurrentApiAccessMethod(apiAccessMethodId) + } + } + + @Test + fun `when calling set current method and testing is not successful should not call set method`() = + runTest { + // Arrange + coEvery { mockApiAccessRepository.testApiAccessMethodById(apiAccessMethodId) } returns + TestApiAccessMethodError.CouldNotAccess.left() + coEvery { mockApiAccessRepository.setCurrentApiAccessMethod(any()) } returns + Unit.right() + + // Act + apiAccessMethodDetailsViewModel.setCurrentMethod() + + // Assert + coVerify(exactly = 0) { + mockApiAccessRepository.setCurrentApiAccessMethod(apiAccessMethodId) + } + } + + @Test + fun `when testing method should update is testing access method to true`() = runTest { + // Arrange + coEvery { mockApiAccessRepository.testApiAccessMethodById(apiAccessMethodId) } coAnswers + { + // Added so that the state gets updated + delay(Duration.ofMillis(1)) + Unit.right() + } + + // Act, Assert + apiAccessMethodDetailsViewModel.uiState.test { + // Default item + awaitItem() + apiAccessMethodDetailsViewModel.testMethod() + val result = awaitItem() + assertIs<ApiAccessMethodDetailsUiState.Content>(result) + assertEquals(true, result.isTestingAccessMethod) + } + } + + @Test + fun `when testing method is successful should send side effect api reached`() = runTest { + // Arrange + coEvery { mockApiAccessRepository.testApiAccessMethodById(apiAccessMethodId) } returns + Unit.right() + + // Act, Assert + apiAccessMethodDetailsViewModel.uiSideEffect.test { + apiAccessMethodDetailsViewModel.testMethod() + val result = awaitItem() + assertIs<ApiAccessMethodDetailsSideEffect.TestApiAccessMethodResult>(result) + assertEquals(true, result.successful) + } + } + + @Test + fun `when testing method is not successful should send side effect api not reached`() = + runTest { + // Arrange + coEvery { mockApiAccessRepository.testApiAccessMethodById(apiAccessMethodId) } returns + TestApiAccessMethodError.CouldNotAccess.left() + + // Act, Assert + apiAccessMethodDetailsViewModel.uiSideEffect.test { + apiAccessMethodDetailsViewModel.testMethod() + val result = awaitItem() + assertIs<ApiAccessMethodDetailsSideEffect.TestApiAccessMethodResult>(result) + assertEquals(false, result.successful) + } + } + + @Test + fun `when enable access method is successful nothing should happen`() = runTest { + // Arrange + coEvery { + mockApiAccessRepository.setEnabledApiAccessMethod(apiAccessMethodId, true) + } returns Unit.right() + + // Act, Assert + apiAccessMethodDetailsViewModel.uiSideEffect.test { + apiAccessMethodDetailsViewModel.setEnableMethod(true) + expectNoEvents() + } + } + + @Test + fun `when enable access method is not successful should show error`() = runTest { + // Arrange + coEvery { + mockApiAccessRepository.setEnabledApiAccessMethod(apiAccessMethodId, true) + } returns UnknownApiAccessMethodError(Throwable()).left() + + // Act, Assert + apiAccessMethodDetailsViewModel.uiSideEffect.test { + apiAccessMethodDetailsViewModel.setEnableMethod(true) + assertEquals(ApiAccessMethodDetailsSideEffect.GenericError, awaitItem()) + } + } + + @Test + fun `calling open edit page should return side effect with id`() = runTest { + // Act, Assert + apiAccessMethodDetailsViewModel.uiSideEffect.test { + apiAccessMethodDetailsViewModel.openEditPage() + assertEquals( + ApiAccessMethodDetailsSideEffect.OpenEditPage(apiAccessMethodId), + awaitItem() + ) + } + } +} diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteApiAccessMethodConfirmationViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteApiAccessMethodConfirmationViewModelTest.kt new file mode 100644 index 0000000000..18f6a64647 --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteApiAccessMethodConfirmationViewModelTest.kt @@ -0,0 +1,65 @@ +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 kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.data.UUID +import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId +import net.mullvad.mullvadvpn.lib.model.RemoveApiAccessMethodError +import net.mullvad.mullvadvpn.repository.ApiAccessRepository +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(TestCoroutineRule::class) +class DeleteApiAccessMethodConfirmationViewModelTest { + + private val mockApiAccessRepository: ApiAccessRepository = mockk() + private lateinit var deleteApiAccessMethodConfirmationViewModel: + DeleteApiAccessMethodConfirmationViewModel + + @BeforeEach + fun setUp() { + val apiAccessMethodId = ApiAccessMethodId.fromString(UUID) + + deleteApiAccessMethodConfirmationViewModel = + DeleteApiAccessMethodConfirmationViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessRepository = mockApiAccessRepository + ) + } + + @Test + fun `when deleting api access method is successful should update uiSideEffect`() = runTest { + // Arrange + coEvery { mockApiAccessRepository.removeApiAccessMethod(any()) } returns Unit.right() + + // Act, Assert + deleteApiAccessMethodConfirmationViewModel.uiSideEffect.test { + deleteApiAccessMethodConfirmationViewModel.deleteApiAccessMethod() + val result = awaitItem() + assertEquals(DeleteApiAccessMethodConfirmationSideEffect.Deleted, result) + } + } + + @Test + fun `when deleting api access method is not successful should update ui state`() = runTest { + // Arrange + val error = RemoveApiAccessMethodError.Unknown(Throwable()) + coEvery { mockApiAccessRepository.removeApiAccessMethod(any()) } returns error.left() + + // Act, Assert + deleteApiAccessMethodConfirmationViewModel.uiState.test { + // Default item + awaitItem() + deleteApiAccessMethodConfirmationViewModel.deleteApiAccessMethod() + val result = awaitItem().deleteError + assertEquals(error, result) + } + } +} diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SaveApiAccessMethodViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SaveApiAccessMethodViewModelTest.kt new file mode 100644 index 0000000000..0828b3ed08 --- /dev/null +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SaveApiAccessMethodViewModelTest.kt @@ -0,0 +1,221 @@ +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.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import net.mullvad.mullvadvpn.compose.state.SaveApiAccessMethodUiState +import net.mullvad.mullvadvpn.compose.state.TestApiAccessMethodState +import net.mullvad.mullvadvpn.data.UUID +import net.mullvad.mullvadvpn.lib.common.test.TestCoroutineRule +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId +import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodName +import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting +import net.mullvad.mullvadvpn.lib.model.TestApiAccessMethodError +import net.mullvad.mullvadvpn.lib.model.UnknownApiAccessMethodError +import net.mullvad.mullvadvpn.repository.ApiAccessRepository +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(TestCoroutineRule::class) +class SaveApiAccessMethodViewModelTest { + private val mockApiAccessRepository: ApiAccessRepository = mockk() + + private lateinit var saveApiAccessMethodViewModel: SaveApiAccessMethodViewModel + + @Test + fun `when testing and updating an existing method successfully should do the correct steps`() = + runTest { + // Arrange + val apiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val apiAccessMethodName = ApiAccessMethodName.fromString("Name") + val customProxy = mockk<ApiAccessMethod.CustomProxy>() + coEvery { mockApiAccessRepository.testCustomApiAccessMethod(customProxy) } returns + Unit.right() + coEvery { + mockApiAccessRepository.updateApiAccessMethod( + apiAccessMethodId, + apiAccessMethodName, + customProxy + ) + } returns Unit.right() + createSaveApiAccessMethodViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessMethodName = apiAccessMethodName, + customProxy = customProxy + ) + + // Act, Assert + saveApiAccessMethodViewModel.uiState.test { + // After successful test + assertEquals( + SaveApiAccessMethodUiState( + testingState = TestApiAccessMethodState.Result.Successful, + isSaving = true + ), + awaitItem() + ) + } + saveApiAccessMethodViewModel.uiSideEffect.test { + // Check for successful creation + assertEquals( + SaveApiAccessMethodSideEffect.SuccessfullyCreatedApiMethod, + awaitItem() + ) + } + } + + @Test + fun `when testing api access method fail should update ui state`() = runTest { + // Arrange + val apiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val apiAccessMethodName = ApiAccessMethodName.fromString("Name") + val customProxy = mockk<ApiAccessMethod.CustomProxy>() + coEvery { mockApiAccessRepository.testCustomApiAccessMethod(customProxy) } returns + TestApiAccessMethodError.CouldNotAccess.left() + createSaveApiAccessMethodViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessMethodName = apiAccessMethodName, + customProxy = customProxy + ) + + // Act, Assert + saveApiAccessMethodViewModel.uiState.test { + assertEquals( + SaveApiAccessMethodUiState( + testingState = TestApiAccessMethodState.Result.Failure, + isSaving = false + ), + awaitItem() + ) + } + } + + @Test + fun `when saving existing api access method after failure should update ui state`() = runTest { + // Arrange + val apiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val apiAccessMethodName = ApiAccessMethodName.fromString("Name") + val customProxy = mockk<ApiAccessMethod.CustomProxy>() + coEvery { mockApiAccessRepository.testCustomApiAccessMethod(customProxy) } returns + TestApiAccessMethodError.CouldNotAccess.left() + coEvery { + mockApiAccessRepository.updateApiAccessMethod( + apiAccessMethodId, + apiAccessMethodName, + customProxy + ) + } returns Unit.right() + createSaveApiAccessMethodViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessMethodName = apiAccessMethodName, + customProxy = customProxy + ) + + // Act, Assert + saveApiAccessMethodViewModel.uiState.test { + // After successful test + assertEquals( + SaveApiAccessMethodUiState( + testingState = TestApiAccessMethodState.Result.Failure, + isSaving = false + ), + awaitItem() + ) + saveApiAccessMethodViewModel.save() + // Saving + assertEquals( + SaveApiAccessMethodUiState( + testingState = TestApiAccessMethodState.Result.Failure, + isSaving = true + ), + awaitItem() + ) + } + saveApiAccessMethodViewModel.uiSideEffect.test { + // Check for successful creation + assertEquals(SaveApiAccessMethodSideEffect.SuccessfullyCreatedApiMethod, awaitItem()) + } + } + + @Test + fun `when saving is not successful should return side effect failure`() = runTest { + // Arrange + val apiAccessMethodId = ApiAccessMethodId.fromString(UUID) + val apiAccessMethodName = ApiAccessMethodName.fromString("Name") + val customProxy = mockk<ApiAccessMethod.CustomProxy>() + coEvery { mockApiAccessRepository.testCustomApiAccessMethod(customProxy) } returns + Unit.right() + coEvery { + mockApiAccessRepository.updateApiAccessMethod( + apiAccessMethodId, + apiAccessMethodName, + customProxy + ) + } returns UnknownApiAccessMethodError(Throwable()).left() + createSaveApiAccessMethodViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessMethodName = apiAccessMethodName, + customProxy = customProxy + ) + + // Act, Assert + saveApiAccessMethodViewModel.uiSideEffect.test { + assertEquals(SaveApiAccessMethodSideEffect.CouldNotSaveApiAccessMethod, awaitItem()) + } + } + + @Test + fun `when saving a new api access method should call addApiAccessMethod`() = runTest { + // Arrange + val apiAccessMethodId = null + val apiAccessMethodName = ApiAccessMethodName.fromString("Name") + val customProxy = mockk<ApiAccessMethod.CustomProxy>() + coEvery { mockApiAccessRepository.testCustomApiAccessMethod(customProxy) } returns + Unit.right() + coEvery { + mockApiAccessRepository.addApiAccessMethod( + NewAccessMethodSetting( + name = apiAccessMethodName, + enabled = true, + apiAccessMethod = customProxy + ) + ) + } returns ApiAccessMethodId.fromString(UUID).right() + createSaveApiAccessMethodViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessMethodName = apiAccessMethodName, + customProxy = customProxy + ) + + // Assert + coVerify(exactly = 1) { + mockApiAccessRepository.addApiAccessMethod( + NewAccessMethodSetting( + name = apiAccessMethodName, + enabled = true, + apiAccessMethod = customProxy + ) + ) + } + } + + private fun createSaveApiAccessMethodViewModel( + apiAccessMethodId: ApiAccessMethodId?, + apiAccessMethodName: ApiAccessMethodName, + customProxy: ApiAccessMethod.CustomProxy + ) { + saveApiAccessMethodViewModel = + SaveApiAccessMethodViewModel( + apiAccessMethodId = apiAccessMethodId, + apiAccessMethodName = apiAccessMethodName, + customProxy = customProxy, + apiAccessRepository = mockApiAccessRepository + ) + } +} |
