summaryrefslogtreecommitdiffhomepage
path: root/android/test/e2e/src
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2023-01-10 15:58:10 +0100
committerAlbin <albin@mullvad.net>2023-01-10 15:58:10 +0100
commit7ea38a3881b92b17658a5be4f0e627601212db6c (patch)
tree96c12212b3f95c7ea588099aec7cb5fdb22942ed /android/test/e2e/src
parentfee3b5804555b3287c9c59aecd3682f118735ba8 (diff)
parent57008a509342f547f5e56ef781a7f2e8a18298c0 (diff)
downloadmullvadvpn-7ea38a3881b92b17658a5be4f0e627601212db6c.tar.xz
mullvadvpn-7ea38a3881b92b17658a5be4f0e627601212db6c.zip
Merge branch 'add-instrumented-tests-using-mocked-api'
Diffstat (limited to 'android/test/e2e/src')
-rw-r--r--android/test/e2e/src/main/AndroidManifest.xml10
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt38
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt54
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt13
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt60
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt27
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/ConnCheckConstants.kt3
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt5
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/TextConstants.kt4
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/extension/BundleExtensions.kt8
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/MullvadAccountInteractor.kt12
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/SystemSettingsInteractor.kt36
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt21
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ConnCheckState.kt6
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/SimpleMullvadHttpClient.kt208
15 files changed, 505 insertions, 0 deletions
diff --git a/android/test/e2e/src/main/AndroidManifest.xml b/android/test/e2e/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..9ba07f4905
--- /dev/null
+++ b/android/test/e2e/src/main/AndroidManifest.xml
@@ -0,0 +1,10 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+ <uses-permission android:name="android.permission.INTERNET" />
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="net.mullvad.mullvadvpn" />
+</manifest>
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
new file mode 100644
index 0000000000..8f72fef0be
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
@@ -0,0 +1,38 @@
+package net.mullvad.mullvadvpn.test.e2e
+
+import androidx.test.uiautomator.By
+import junit.framework.Assert.assertEquals
+import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
+import net.mullvad.mullvadvpn.test.common.rule.ForgetAllVpnAppsInSettingsTestRule
+import net.mullvad.mullvadvpn.test.e2e.misc.CleanupAccountTestRule
+import net.mullvad.mullvadvpn.test.e2e.misc.ConnCheckState
+import net.mullvad.mullvadvpn.test.e2e.misc.SimpleMullvadHttpClient
+import org.junit.Rule
+import org.junit.Test
+
+class ConnectionTest : EndToEndTest() {
+
+ @Rule
+ @JvmField
+ val cleanupAccountTestRule = CleanupAccountTestRule()
+
+ @Rule
+ @JvmField
+ val forgetAllVpnAppsInSettingsTestRule = ForgetAllVpnAppsInSettingsTestRule()
+
+ @Test
+ fun testConnectAndVerifyWithConnectionCheck() {
+ // Given
+ app.launchAndEnsureLoggedIn(validTestAccountToken)
+
+ // When
+ device.findObjectWithTimeout(By.text("Secure my connection")).click()
+ device.findObjectWithTimeout(By.text("OK")).click()
+ device.findObjectWithTimeout(By.text("SECURE CONNECTION"))
+ val expected = ConnCheckState(true, app.extractIpAddress())
+
+ // Then
+ val result = SimpleMullvadHttpClient(targetContext).runConnectionCheck()
+ assertEquals(expected, result)
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt
new file mode 100644
index 0000000000..f8f5bb8f6c
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/EndToEndTest.kt
@@ -0,0 +1,54 @@
+package net.mullvad.mullvadvpn.test.e2e
+
+import android.Manifest
+import android.content.Context
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.rule.GrantPermissionRule
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.UiDevice
+import net.mullvad.mullvadvpn.test.common.interactor.AppInteractor
+import net.mullvad.mullvadvpn.test.common.rule.CaptureScreenshotOnFailedTestRule
+import net.mullvad.mullvadvpn.test.e2e.constant.INVALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY
+import net.mullvad.mullvadvpn.test.e2e.constant.LOG_TAG
+import net.mullvad.mullvadvpn.test.e2e.constant.VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY
+import net.mullvad.mullvadvpn.test.e2e.extension.getRequiredArgument
+import org.junit.Before
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+abstract class EndToEndTest {
+
+ @Rule
+ @JvmField
+ val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG)
+
+ @Rule
+ @JvmField
+ val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ )
+
+ lateinit var device: UiDevice
+ lateinit var targetContext: Context
+ lateinit var app: AppInteractor
+ lateinit var validTestAccountToken: String
+ lateinit var invalidTestAccountToken: String
+
+ @Before
+ fun setup() {
+ device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ targetContext = InstrumentationRegistry.getInstrumentation().targetContext
+
+ validTestAccountToken = InstrumentationRegistry.getArguments()
+ .getRequiredArgument(VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY)
+ invalidTestAccountToken = InstrumentationRegistry.getArguments()
+ .getRequiredArgument(INVALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY)
+
+ app = AppInteractor(
+ device,
+ targetContext
+ )
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt
new file mode 100644
index 0000000000..64f534990c
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LaunchAppTest.kt
@@ -0,0 +1,13 @@
+package net.mullvad.mullvadvpn.test.e2e
+
+import androidx.test.runner.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LaunchAppTest : EndToEndTest() {
+ @Test
+ fun testLaunchApp() {
+ app.launch()
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt
new file mode 100644
index 0000000000..9e106d45a2
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/LoginTest.kt
@@ -0,0 +1,60 @@
+package net.mullvad.mullvadvpn.test.e2e
+
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import junit.framework.Assert.assertNotNull
+import net.mullvad.mullvadvpn.test.common.constant.LOGIN_FAILURE_TIMEOUT
+import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove
+import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
+import net.mullvad.mullvadvpn.test.e2e.misc.CleanupAccountTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LoginTest : EndToEndTest() {
+
+ @Rule
+ @JvmField
+ val cleanupAccountTestRule = CleanupAccountTestRule()
+
+ @Test
+ fun testLoginWithInvalidCredentials() {
+ // Given
+ val invalidDummyAccountToken = invalidTestAccountToken
+
+ // When
+ app.launch()
+ device.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove()
+ app.attemptLogin(invalidDummyAccountToken)
+
+ // Then
+ device.findObjectWithTimeout(By.text("Login failed"), LOGIN_FAILURE_TIMEOUT)
+ }
+
+ @Test
+ fun testLoginWithValidCredentials() {
+ // Given
+ val token = validTestAccountToken
+
+ // When
+ app.launchAndEnsureLoggedIn(token)
+
+ // Then
+ app.ensureLoggedIn()
+ }
+
+ @Test
+ fun testLogout() {
+ // Given
+ app.launchAndEnsureLoggedIn(validTestAccountToken)
+
+ // When
+ app.clickSettingsCog()
+ app.clickListItemByText("Account")
+ app.clickActionButtonByText("Log out")
+
+ // Then
+ assertNotNull(device.findObjectWithTimeout(By.text("Login")))
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt
new file mode 100644
index 0000000000..9893074a88
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/WebLinkTest.kt
@@ -0,0 +1,27 @@
+package net.mullvad.mullvadvpn.test.e2e
+
+import androidx.test.uiautomator.By
+import net.mullvad.mullvadvpn.test.common.constant.WEB_TIMEOUT
+import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove
+import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
+import org.junit.Test
+
+class WebLinkTest : EndToEndTest() {
+ @Test
+ fun testOpenFaqFromApp() {
+ // Given
+ app.launch()
+
+ // When
+ device.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove()
+ device.findObjectWithTimeout(By.text("Login"))
+ app.clickSettingsCog()
+ app.clickListItemByText("FAQs & Guides")
+
+ // Then
+ device.findObjectWithTimeout(
+ selector = By.text("Mullvad help center"),
+ timeout = WEB_TIMEOUT
+ )
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/ConnCheckConstants.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/ConnCheckConstants.kt
new file mode 100644
index 0000000000..5357ce0e75
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/ConnCheckConstants.kt
@@ -0,0 +1,3 @@
+package net.mullvad.mullvadvpn.test.e2e.constant
+
+const val CONN_CHECK_URL = "https://am.i.mullvad.net/json"
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt
new file mode 100644
index 0000000000..98fd52a333
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt
@@ -0,0 +1,5 @@
+package net.mullvad.mullvadvpn.test.e2e.constant
+
+const val LOG_TAG = "mullvad-e2e"
+const val VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY = "valid_test_account_token"
+const val INVALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY = "invalid_test_account_token"
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/TextConstants.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/TextConstants.kt
new file mode 100644
index 0000000000..cfc4080ea4
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/TextConstants.kt
@@ -0,0 +1,4 @@
+package net.mullvad.mullvadvpn.test.e2e.constant
+
+const val CONNECTION_CHECK_IS_CONNECTED = "Using Mullvad VPN"
+const val CONNECTION_CHECK_IS_NOT_CONNECTED = "Not using Mullvad VPN"
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/extension/BundleExtensions.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/extension/BundleExtensions.kt
new file mode 100644
index 0000000000..c96c28bb09
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/extension/BundleExtensions.kt
@@ -0,0 +1,8 @@
+package net.mullvad.mullvadvpn.test.e2e.extension
+
+import android.os.Bundle
+
+fun Bundle.getRequiredArgument(argument: String): String {
+ return getString(argument)
+ ?: throw IllegalArgumentException("Missing required argument: $argument")
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/MullvadAccountInteractor.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/MullvadAccountInteractor.kt
new file mode 100644
index 0000000000..8f3f55166f
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/MullvadAccountInteractor.kt
@@ -0,0 +1,12 @@
+package net.mullvad.mullvadvpn.test.e2e.interactor
+
+import net.mullvad.mullvadvpn.test.e2e.misc.SimpleMullvadHttpClient
+
+class MullvadAccountInteractor(
+ private val httpClient: SimpleMullvadHttpClient,
+ private val testAccountToken: String
+) {
+ fun cleanupAccount() {
+ httpClient.removeAllDevices(testAccountToken)
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/SystemSettingsInteractor.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/SystemSettingsInteractor.kt
new file mode 100644
index 0000000000..9e5f0aa665
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/interactor/SystemSettingsInteractor.kt
@@ -0,0 +1,36 @@
+package net.mullvad.mullvadvpn.test.e2e.interactor
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import net.mullvad.mullvadvpn.test.common.extension.findObjectByCaseInsensitiveText
+
+class SystemSettingsInteractor(
+ private val uiDevice: UiDevice,
+ private val context: Context
+) {
+ fun openVpnSettings() {
+ val intent = Intent("com.intent.MAIN").apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ intent.component = ComponentName.unflattenFromString(
+ "com.android.settings/.Settings\$VpnSettingsActivity"
+ )
+ context.startActivity(intent)
+ Thread.sleep(1000)
+ }
+
+ fun removeAllVpnPermissions() {
+ openVpnSettings()
+ uiDevice.findObjects(By.descContains("Settings")).forEach {
+ it.click()
+ Thread.sleep(1000)
+ uiDevice.findObjectByCaseInsensitiveText("forget vpn").click()
+ Thread.sleep(1000)
+ uiDevice.findObjectByCaseInsensitiveText("forget").click()
+ }
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt
new file mode 100644
index 0000000000..2b69436f6d
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/CleanupAccountTestRule.kt
@@ -0,0 +1,21 @@
+package net.mullvad.mullvadvpn.test.e2e.misc
+
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import net.mullvad.mullvadvpn.test.e2e.constant.LOG_TAG
+import net.mullvad.mullvadvpn.test.e2e.constant.VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY
+import net.mullvad.mullvadvpn.test.e2e.extension.getRequiredArgument
+import net.mullvad.mullvadvpn.test.e2e.interactor.MullvadAccountInteractor
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+class CleanupAccountTestRule : TestWatcher() {
+ override fun starting(description: Description) {
+ Log.d(LOG_TAG, "Cleaning up account before test: ${description.methodName}")
+ val targetContext = InstrumentationRegistry.getInstrumentation().targetContext
+ val validTestAccountToken = InstrumentationRegistry.getArguments()
+ .getRequiredArgument(VALID_TEST_ACCOUNT_TOKEN_ARGUMENT_KEY)
+ MullvadAccountInteractor(SimpleMullvadHttpClient(targetContext), validTestAccountToken)
+ .cleanupAccount()
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ConnCheckState.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ConnCheckState.kt
new file mode 100644
index 0000000000..744e80124e
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/ConnCheckState.kt
@@ -0,0 +1,6 @@
+package net.mullvad.mullvadvpn.test.e2e.misc
+
+data class ConnCheckState(
+ val isConnected: Boolean,
+ val ipAddress: String
+)
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/SimpleMullvadHttpClient.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/SimpleMullvadHttpClient.kt
new file mode 100644
index 0000000000..bc56737b04
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/SimpleMullvadHttpClient.kt
@@ -0,0 +1,208 @@
+package net.mullvad.mullvadvpn.test.e2e.misc
+
+import android.content.Context
+import android.util.Log
+import androidx.test.services.events.TestEventException
+import com.android.volley.Request
+import com.android.volley.toolbox.JsonArrayRequest
+import com.android.volley.toolbox.JsonObjectRequest
+import com.android.volley.toolbox.RequestFuture
+import com.android.volley.toolbox.StringRequest
+import com.android.volley.toolbox.Volley
+import net.mullvad.mullvadvpn.test.e2e.BuildConfig
+import net.mullvad.mullvadvpn.test.e2e.constant.CONN_CHECK_URL
+import net.mullvad.mullvadvpn.test.e2e.constant.LOG_TAG
+import org.json.JSONArray
+import org.json.JSONObject
+
+class SimpleMullvadHttpClient(context: Context) {
+
+ private val queue = Volley.newRequestQueue(context)
+
+ fun removeAllDevices(accountToken: String) {
+ Log.v(LOG_TAG, "Remove all devices")
+ val token = login(accountToken)
+ val devices = getDeviceList(token)
+ devices.forEach {
+ removeDevice(token, it)
+ }
+ Log.v(LOG_TAG, "All devices removed")
+ }
+
+ fun login(accountToken: String): String {
+ Log.v(LOG_TAG, "Attempt login with account token: $accountToken")
+ val json = JSONObject().apply {
+ put("account_number", accountToken)
+ }
+ return sendSimpleSynchronousRequest(Request.Method.POST, AUTH_URL, json)!!.let { response ->
+ response.getString("access_token").also { accessToken ->
+ Log.v(LOG_TAG, "Successfully logged in and received access token: $accessToken")
+ }
+ }
+ }
+
+ fun getDeviceList(accessToken: String): List<String> {
+ Log.v(LOG_TAG, "Get devices")
+
+ val response = sendSimpleSynchronousRequestArray(
+ Request.Method.GET,
+ DEVICE_LIST_URL,
+ token = accessToken
+ )
+
+ return response!!.iterator<JSONObject>().asSequence().toList()
+ .also {
+ it
+ .map { jsonObject ->
+ jsonObject.getString("name")
+ }
+ .also { deviceNames ->
+ Log.v(LOG_TAG, "Devices received: $deviceNames")
+ }
+ }
+ .map { it.getString("id") }
+ .toList()
+ }
+
+ fun removeDevice(token: String, deviceId: String) {
+ Log.v(LOG_TAG, "Remove device: $deviceId")
+ sendSimpleSynchronousRequestString(
+ Request.Method.DELETE,
+ "$DEVICE_LIST_URL/$deviceId",
+ token = token
+ )
+ }
+
+ fun runConnectionCheck(): ConnCheckState? {
+ return sendSimpleSynchronousRequestString(
+ Request.Method.GET,
+ CONN_CHECK_URL
+ )
+ ?.let { respose ->
+ JSONObject(respose)
+ }
+ ?.let { json ->
+ ConnCheckState(
+ isConnected = json.getBoolean("mullvad_exit_ip"),
+ ipAddress = json.getString("ip")
+ )
+ }
+ }
+
+ private fun sendSimpleSynchronousRequest(
+ method: Int,
+ url: String,
+ body: JSONObject? = null,
+ token: String? = null
+ ): JSONObject? {
+ val future = RequestFuture.newFuture<JSONObject>()
+ val request = object : JsonObjectRequest(
+ method,
+ url,
+ body,
+ future,
+ future
+ ) {
+ override fun getHeaders(): MutableMap<String, String> {
+ val headers = HashMap<String, String>()
+ if (body != null) {
+ headers.put("Content-Type", "application/json")
+ }
+ if (token != null) {
+ headers.put("Authorization", "Bearer $token")
+ }
+ return headers
+ }
+ }
+ queue.add(request)
+ return try {
+ future.get().also { response ->
+ Log.v(LOG_TAG, "Json object request response: $response")
+ }
+ } catch (e: Exception) {
+ Log.v(LOG_TAG, "Json object request error: ${e.message}")
+ throw TestEventException(REQUEST_ERROR_MESSAGE)
+ }
+ }
+
+ private fun sendSimpleSynchronousRequestString(
+ method: Int,
+ url: String,
+ body: String? = null,
+ token: String? = null
+ ): String? {
+ val future = RequestFuture.newFuture<String>()
+ val request = object : StringRequest(
+ method,
+ url,
+ future,
+ future
+ ) {
+ override fun getHeaders(): MutableMap<String, String> {
+ val headers = HashMap<String, String>()
+ if (body != null) {
+ headers.put("Content-Type", "application/json")
+ }
+ if (token != null) {
+ headers.put("Authorization", "Bearer $token")
+ }
+ return headers
+ }
+ }
+ queue.add(request)
+ return try {
+ future.get().also { response ->
+ Log.v(LOG_TAG, "String request response: $response")
+ }
+ } catch (e: Exception) {
+ Log.v(LOG_TAG, "String request error: ${e.message}")
+ throw TestEventException(REQUEST_ERROR_MESSAGE)
+ }
+ }
+
+ private fun sendSimpleSynchronousRequestArray(
+ method: Int,
+ url: String,
+ body: JSONArray? = null,
+ token: String? = null
+ ): JSONArray? {
+ val future = RequestFuture.newFuture<JSONArray>()
+ val request = object : JsonArrayRequest(
+ method,
+ url,
+ null,
+ future,
+ future
+ ) {
+ override fun getHeaders(): MutableMap<String, String> {
+ val headers = HashMap<String, String>()
+ headers.put("Content-Type", "application/json")
+ if (token != null) {
+ headers.put("Authorization", "Bearer $token")
+ }
+ return headers
+ }
+ }
+ queue.add(request)
+ return try {
+ future.get().also { response ->
+ Log.v(LOG_TAG, "Json array request response: $response")
+ }
+ } catch (e: Exception) {
+ Log.v(LOG_TAG, "Json array request error: ${e.message}")
+ throw TestEventException(REQUEST_ERROR_MESSAGE)
+ }
+ }
+
+ operator fun <T> JSONArray.iterator(): Iterator<T> =
+ (0 until this.length()).asSequence().map { this.get(it) as T }.iterator()
+
+ companion object {
+ private const val AUTH_URL =
+ "${BuildConfig.API_BASE_URL}/auth/${BuildConfig.API_VERSION}/token"
+ private const val DEVICE_LIST_URL =
+ "${BuildConfig.API_BASE_URL}/accounts/${BuildConfig.API_VERSION}/devices"
+ private const val REQUEST_ERROR_MESSAGE =
+ "Unable to verify account due to invalid account or connectivity issues."
+ }
+}