summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/androidTest
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-13 16:39:26 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-14 15:01:20 +0200
commitc0361dbef8c76fdea544d7733f29efe653d75b9e (patch)
tree8a70465bae5671a9ee76ea188e664eba580b46a2 /android/app/src/androidTest
parent4e2c10994a7bd7916e74aedbe8f90e0da67d1a42 (diff)
downloadmullvadvpn-c0361dbef8c76fdea544d7733f29efe653d75b9e.tar.xz
mullvadvpn-c0361dbef8c76fdea544d7733f29efe653d75b9e.zip
Add ui tests for api access method
Diffstat (limited to 'android/app/src/androidTest')
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt33
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/SaveApiAccessMethodDialogTest.kt125
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessListScreenTest.kt111
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt226
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreenTest.kt257
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt2
6 files changed, 754 insertions, 0 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt
new file mode 100644
index 0000000000..a644449a91
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyApiAccessMethods.kt
@@ -0,0 +1,33 @@
+package net.mullvad.mullvadvpn.compose.data
+
+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.Cipher
+import net.mullvad.mullvadvpn.lib.model.Port
+
+private const val UUID1 = "12345678-1234-5678-1234-567812345678"
+private const val UUID2 = "12345678-1234-5678-1234-567812345679"
+
+val DIRECT_ACCESS_METHOD =
+ ApiAccessMethodSetting(
+ id = ApiAccessMethodId.fromString(UUID1),
+ name = ApiAccessMethodName.fromString("Direct"),
+ enabled = true,
+ apiAccessMethod = ApiAccessMethod.Direct
+ )
+
+val CUSTOM_ACCESS_METHOD =
+ ApiAccessMethodSetting(
+ id = ApiAccessMethodId.fromString(UUID2),
+ name = ApiAccessMethodName.fromString("ShadowSocks"),
+ enabled = true,
+ apiAccessMethod =
+ ApiAccessMethod.CustomProxy.Shadowsocks(
+ ip = "1.1.1.1",
+ port = Port(123),
+ password = "Password",
+ cipher = Cipher.RC4
+ )
+ )
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/SaveApiAccessMethodDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/SaveApiAccessMethodDialogTest.kt
new file mode 100644
index 0000000000..07f8b039eb
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/SaveApiAccessMethodDialogTest.kt
@@ -0,0 +1,125 @@
+package net.mullvad.mullvadvpn.compose.dialog
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.SaveApiAccessMethodUiState
+import net.mullvad.mullvadvpn.compose.state.TestApiAccessMethodState
+import net.mullvad.mullvadvpn.compose.test.SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.SAVE_API_ACCESS_METHOD_LOADING_SPINNER_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.SAVE_API_ACCESS_METHOD_SAVE_BUTTON_TEST_TAG
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class SaveApiAccessMethodDialogTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @Test
+ fun whenTestingInProgressShouldShowSpinnerWithCancelButton() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ SaveApiAccessMethodDialog(
+ state =
+ SaveApiAccessMethodUiState(
+ testingState = TestApiAccessMethodState.Testing,
+ isSaving = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_LOADING_SPINNER_TEST_TAG).assertExists()
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun whenTestingFailedShouldShowSaveAndCancelButton() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ SaveApiAccessMethodDialog(
+ state =
+ SaveApiAccessMethodUiState(
+ testingState = TestApiAccessMethodState.Result.Failure,
+ isSaving = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_SAVE_BUTTON_TEST_TAG).assertExists()
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG).assertExists()
+ }
+
+ @Test
+ fun whenTestingSuccessfulAndSavingShouldShowDisabledCancelButton() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ SaveApiAccessMethodDialog(
+ state =
+ SaveApiAccessMethodUiState(
+ testingState = TestApiAccessMethodState.Result.Successful,
+ isSaving = true
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG).assertExists()
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG).assertIsNotEnabled()
+ }
+
+ @Test
+ fun whenTestingInProgressAndClickingCancelShouldCallOnCancel() =
+ composeExtension.use {
+ // Arrange
+ val onCancelClick: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ SaveApiAccessMethodDialog(
+ state =
+ SaveApiAccessMethodUiState(
+ testingState = TestApiAccessMethodState.Testing,
+ isSaving = false
+ ),
+ onCancel = onCancelClick
+ )
+ }
+
+ // Act
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_CANCEL_BUTTON_TEST_TAG).performClick()
+
+ // Assert
+ verify { onCancelClick() }
+ }
+
+ @Test
+ fun whenTestingFailedAndClickingSaveShouldCallOnSave() =
+ composeExtension.use {
+ // Arrange
+ val onSaveClick: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ SaveApiAccessMethodDialog(
+ state =
+ SaveApiAccessMethodUiState(
+ testingState = TestApiAccessMethodState.Result.Failure,
+ isSaving = false
+ ),
+ onSave = onSaveClick
+ )
+ }
+
+ // Act
+ onNodeWithTag(SAVE_API_ACCESS_METHOD_SAVE_BUTTON_TEST_TAG).performClick()
+
+ // Assert
+ verify { onSaveClick() }
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessListScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessListScreenTest.kt
new file mode 100644
index 0000000000..97eed92d91
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessListScreenTest.kt
@@ -0,0 +1,111 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.data.DIRECT_ACCESS_METHOD
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.ApiAccessListUiState
+import net.mullvad.mullvadvpn.compose.test.API_ACCESS_LIST_INFO_TEST_TAG
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class ApiAccessListScreenTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @Test
+ fun shouldShowCurrentApiAccessName() =
+ composeExtension.use {
+ // Arrange
+ val currentApiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessListScreen(
+ state =
+ ApiAccessListUiState(currentApiAccessMethodSetting = currentApiAccessMethod)
+ )
+ }
+
+ // Assert
+ onNodeWithText("Current: ${currentApiAccessMethod.name}")
+ }
+
+ @Test
+ fun shouldShowApiAccessNameAndStatusInList() =
+ composeExtension.use {
+ // Arrange
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessListScreen(
+ state = ApiAccessListUiState(apiAccessMethodSettings = listOf(apiAccessMethod))
+ )
+ }
+
+ // Assert
+ onNodeWithText(apiAccessMethod.name.value)
+ onNodeWithText("On")
+ }
+
+ @Test
+ fun whenClickingOnAddMethodShouldCallOnAddMethodClicked() =
+ composeExtension.use {
+ // Arrange
+ val onAddMethodClick: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ ApiAccessListScreen(
+ state = ApiAccessListUiState(),
+ onAddMethodClick = onAddMethodClick
+ )
+ }
+
+ // Act
+ onNodeWithText("Add").performClick()
+
+ // Assert
+ verify { onAddMethodClick() }
+ }
+
+ @Test
+ fun whenClickingOnInfoButtonShouldCallOnApiAccessInfoClick() =
+ composeExtension.use {
+ // Arrange
+ val onApiAccessInfoClick: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ ApiAccessListScreen(
+ state = ApiAccessListUiState(),
+ onApiAccessInfoClick = onApiAccessInfoClick
+ )
+ }
+
+ // Act
+ onNodeWithTag(API_ACCESS_LIST_INFO_TEST_TAG).performClick()
+
+ // Assert
+ verify { onApiAccessInfoClick() }
+ }
+
+ @Test
+ fun whenClickingOnApiAccessMethodShouldCallOnApiAccessMethodClickWithCorrectAccessMethod() =
+ composeExtension.use {
+ // Arrange
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ val onApiAccessMethodClick: (ApiAccessMethodSetting) -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ ApiAccessListScreen(
+ state = ApiAccessListUiState(apiAccessMethodSettings = listOf(apiAccessMethod)),
+ onApiAccessMethodClick = onApiAccessMethodClick
+ )
+ }
+
+ // Act
+ onNodeWithText(apiAccessMethod.name.value).performClick()
+
+ // Assert
+ verify { onApiAccessMethodClick(apiAccessMethod) }
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt
new file mode 100644
index 0000000000..8dbd8f9832
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/ApiAccessMethodDetailsScreenTest.kt
@@ -0,0 +1,226 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.data.CUSTOM_ACCESS_METHOD
+import net.mullvad.mullvadvpn.compose.data.DIRECT_ACCESS_METHOD
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.ApiAccessMethodDetailsUiState
+import net.mullvad.mullvadvpn.compose.test.API_ACCESS_DETAILS_EDIT_BUTTON
+import net.mullvad.mullvadvpn.compose.test.API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG
+import net.mullvad.mullvadvpn.compose.test.API_ACCESS_TEST_METHOD_BUTTON
+import net.mullvad.mullvadvpn.compose.test.API_ACCESS_USE_METHOD_BUTTON
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class ApiAccessMethodDetailsScreenTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @Test
+ fun whenApiAccessMethodIsNotEditableShouldNotShowDeleteAndEdit() =
+ composeExtension.use {
+ // Arrange
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = false,
+ isDisableable = true,
+ isCurrentMethod = true,
+ isTestingAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithTag(API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG).assertDoesNotExist()
+ onNodeWithTag(API_ACCESS_DETAILS_EDIT_BUTTON).assertDoesNotExist()
+ }
+
+ @Test
+ fun whenApiAccessMethodIsNotDisableableShouldNotBeAbleDisable() =
+ composeExtension.use {
+ // Arrange
+ val onEnableClicked: (Boolean) -> Unit = mockk(relaxed = true)
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = false,
+ isDisableable = false,
+ isCurrentMethod = true,
+ isTestingAccessMethod = false
+ ),
+ onEnableClicked = onEnableClicked
+ )
+ }
+
+ // Act
+ onNodeWithText("Enable method").performClick()
+
+ // Assert
+ onNodeWithText("At least one method needs to be enabled")
+ verify(exactly = 0) { onEnableClicked(any()) }
+ }
+
+ @Test
+ fun whenClickingOnDeleteMethodShouldCallOnDeleteApiAccessMethodClicked() =
+ composeExtension.use {
+ // Arrange
+ val onDeleteApiAccessMethodClicked: (ApiAccessMethodId) -> Unit = mockk(relaxed = true)
+ val apiAccessMethod = CUSTOM_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = true,
+ isDisableable = false,
+ isCurrentMethod = true,
+ isTestingAccessMethod = false
+ ),
+ onDeleteApiAccessMethodClicked = onDeleteApiAccessMethodClicked
+ )
+ }
+
+ // Act
+ onNodeWithTag(API_ACCESS_DETAILS_TOP_BAR_DROPDOWN_BUTTON_TEST_TAG).performClick()
+ onNodeWithText("Delete method").performClick()
+
+ // Assert
+ verify(exactly = 1) { onDeleteApiAccessMethodClicked(apiAccessMethod.id) }
+ }
+
+ @Test
+ fun whenClickingOnEditMethodShouldCallOnEditMethodClicked() =
+ composeExtension.use {
+ // Arrange
+ val onEditMethodClicked: () -> Unit = mockk(relaxed = true)
+ val apiAccessMethod = CUSTOM_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = true,
+ isDisableable = false,
+ isCurrentMethod = true,
+ isTestingAccessMethod = false
+ ),
+ onEditMethodClicked = onEditMethodClicked
+ )
+ }
+
+ // Act
+ onNodeWithTag(API_ACCESS_DETAILS_EDIT_BUTTON).performClick()
+
+ // Assert
+ verify(exactly = 1) { onEditMethodClicked() }
+ }
+
+ @Test
+ fun whenClickingOnEnableMethodShouldCallOnEnableClicked() =
+ composeExtension.use {
+ // Arrange
+ val onEnableClicked: (Boolean) -> Unit = mockk(relaxed = true)
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = false,
+ isDisableable = true,
+ isCurrentMethod = true,
+ isTestingAccessMethod = false
+ ),
+ onEnableClicked = onEnableClicked
+ )
+ }
+
+ // Act
+ onNodeWithText("Enable method").performClick()
+
+ // Assert
+ verify(exactly = 1) { onEnableClicked(false) }
+ }
+
+ @Test
+ fun whenClickingOnTestMethodShouldCallOnTestMethodClicked() =
+ composeExtension.use {
+ // Arrange
+ val onTestMethodClicked: () -> Unit = mockk(relaxed = true)
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = false,
+ isDisableable = true,
+ isCurrentMethod = true,
+ isTestingAccessMethod = false
+ ),
+ onTestMethodClicked = onTestMethodClicked
+ )
+ }
+
+ // Act
+ onNodeWithTag(API_ACCESS_TEST_METHOD_BUTTON).performClick()
+
+ // Assert
+ verify(exactly = 1) { onTestMethodClicked() }
+ }
+
+ @Test
+ fun whenClickingOnUseMethodShouldCallOnUseMethodClicked() =
+ composeExtension.use {
+ // Arrange
+ val onUseMethodClicked: () -> Unit = mockk(relaxed = true)
+ val apiAccessMethod = DIRECT_ACCESS_METHOD
+ setContentWithTheme {
+ ApiAccessMethodDetailsScreen(
+ state =
+ ApiAccessMethodDetailsUiState.Content(
+ apiAccessMethodId = apiAccessMethod.id,
+ name = apiAccessMethod.name,
+ enabled = apiAccessMethod.enabled,
+ isEditable = false,
+ isDisableable = true,
+ isCurrentMethod = false,
+ isTestingAccessMethod = false
+ ),
+ onUseMethodClicked = onUseMethodClicked
+ )
+ }
+
+ // Act
+ onNodeWithTag(API_ACCESS_USE_METHOD_BUTTON).performClick()
+
+ // Assert
+ verify(exactly = 1) { onUseMethodClicked() }
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreenTest.kt
new file mode 100644
index 0000000000..584be7e074
--- /dev/null
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/EditApiAccessMethodScreenTest.kt
@@ -0,0 +1,257 @@
+package net.mullvad.mullvadvpn.compose.screen
+
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import io.mockk.mockk
+import io.mockk.verify
+import net.mullvad.mullvadvpn.compose.createEdgeToEdgeComposeExtension
+import net.mullvad.mullvadvpn.compose.setContentWithTheme
+import net.mullvad.mullvadvpn.compose.state.ApiAccessMethodTypes
+import net.mullvad.mullvadvpn.compose.state.EditApiAccessFormData
+import net.mullvad.mullvadvpn.compose.state.EditApiAccessMethodUiState
+import net.mullvad.mullvadvpn.compose.test.EDIT_API_ACCESS_NAME_INPUT
+import net.mullvad.mullvadvpn.lib.model.InvalidDataError
+import net.mullvad.mullvadvpn.lib.model.ParsePortError
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+@OptIn(ExperimentalTestApi::class)
+class EditApiAccessMethodScreenTest {
+ @JvmField @RegisterExtension val composeExtension = createEdgeToEdgeComposeExtension()
+
+ @Test
+ fun whenInEditModeAddButtonShouldSaySave() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = true,
+ formData = EditApiAccessFormData.empty(),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText("Save").assertExists()
+ }
+
+ @Test
+ fun whenNotInEditModeAddButtonShouldSayAdd() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData = EditApiAccessFormData.empty(),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText("Add").assertExists()
+ }
+
+ @Test
+ fun whenNameInputHasErrorShouldShowError() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData =
+ EditApiAccessFormData(
+ name = "",
+ nameError = InvalidDataError.NameError.Required,
+ serverIp = "",
+ username = "",
+ password = "",
+ port = ""
+ ),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText("This field is required").assertExists()
+ }
+
+ @Test
+ fun whenServerInputIsNotIpAddressShouldShowError() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData =
+ EditApiAccessFormData(
+ name = "",
+ serverIp = "123",
+ serverIpError = InvalidDataError.ServerIpError.Invalid,
+ username = "",
+ password = "",
+ port = ""
+ ),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText("Please enter a valid IPv4 or IPv6 address").assertExists()
+ }
+
+ @Test
+ fun whenPortInputIsNotWithinRangeShouldShowError() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData =
+ EditApiAccessFormData(
+ name = "",
+ serverIp = "",
+ username = "",
+ password = "",
+ port = "1111111111",
+ portError =
+ InvalidDataError.PortError.Invalid(
+ ParsePortError.OutOfRange(1111111111)
+ )
+ ),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText("Please enter a valid remote server port").assertExists()
+ }
+
+ @Test
+ fun whenNameInputChangesShouldCallOnNameChanged() =
+ composeExtension.use {
+ // Arrange
+ val onNameChanged: (String) -> Unit = mockk(relaxed = true)
+ val mockInput = "Name"
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData = EditApiAccessFormData.empty(),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ ),
+ onNameChanged = onNameChanged
+ )
+ }
+
+ // Act
+ onNodeWithTag(EDIT_API_ACCESS_NAME_INPUT).performTextInput(mockInput)
+
+ // Assert
+ verify(exactly = 1) { onNameChanged(mockInput) }
+ }
+
+ @Test
+ fun whenSocks5IsSelectedAndAuthenticationIsEnabledShouldShowUsernameAndPassword() =
+ composeExtension.use {
+ // Arrange
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData =
+ EditApiAccessFormData(
+ name = "",
+ serverIp = "",
+ username = "",
+ password = "",
+ port = "",
+ enableAuthentication = true,
+ apiAccessMethodTypes = ApiAccessMethodTypes.SOCKS5_REMOTE
+ ),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ )
+ )
+ }
+
+ // Assert
+ onNodeWithText("Username").assertExists()
+ onNodeWithText("Password").assertExists()
+ }
+
+ @Test
+ fun whenClickingOnTestMethodButtonShouldCallOnTestMethod() =
+ composeExtension.use {
+ // Arrange
+ val onTestMethod: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData = EditApiAccessFormData.empty(),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ ),
+ onTestMethod = onTestMethod
+ )
+ }
+
+ // Act
+ onNodeWithText("Test method").performClick()
+
+ // Assert
+ verify(exactly = 1) { onTestMethod() }
+ }
+
+ @Test
+ fun whenClickingOnAddMethodButtonShouldCallOnAddMethod() =
+ composeExtension.use {
+ // Arrange
+ val onAddMethod: () -> Unit = mockk(relaxed = true)
+ setContentWithTheme {
+ EditApiAccessMethodScreen(
+ state =
+ EditApiAccessMethodUiState.Content(
+ editMode = false,
+ formData = EditApiAccessFormData.empty(),
+ hasChanges = false,
+ isTestingApiAccessMethod = false
+ ),
+ onAddMethod = onAddMethod
+ )
+ }
+
+ // Act
+ onNodeWithText("Add").performClick()
+
+ // Assert
+ verify(exactly = 1) { onAddMethod() }
+ }
+}
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt
index b8b6a4e4b8..afa144f405 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/SettingsScreenTest.kt
@@ -40,6 +40,7 @@ class SettingsScreenTest {
onNodeWithText("VPN settings").assertExists()
onNodeWithText("Split tunneling").assertExists()
onNodeWithText("App version").assertExists()
+ onNodeWithText("API access").assertExists()
}
@Test
@@ -62,5 +63,6 @@ class SettingsScreenTest {
onNodeWithText("VPN settings").assertDoesNotExist()
onNodeWithText("Split tunneling").assertDoesNotExist()
onNodeWithText("App version").assertExists()
+ onNodeWithText("API access").assertExists()
}
}