summaryrefslogtreecommitdiffhomepage
path: root/android/app
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-13 16:38:16 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-14 13:27:05 +0200
commit4e2c10994a7bd7916e74aedbe8f90e0da67d1a42 (patch)
tree17f829e79067266675dce15e39b63b06b6e619b2 /android/app
parent7dd2345f65acb9fe9a9b57809603db3a65417a8b (diff)
downloadmullvadvpn-4e2c10994a7bd7916e74aedbe8f90e0da67d1a42.tar.xz
mullvadvpn-4e2c10994a7bd7916e74aedbe8f90e0da67d1a42.zip
Add unit tests for Api access method
Diffstat (limited to 'android/app')
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/data/DummyUUID.kt3
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/repository/ApiAccessRepositoryTest.kt280
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/NewDeviceUseNotificationCaseTest.kt5
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModelTest.kt2
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/ApiAccessMethodDetailsViewModelTest.kt180
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/DeleteApiAccessMethodConfirmationViewModelTest.kt65
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/SaveApiAccessMethodViewModelTest.kt221
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
+ )
+ }
+}