summaryrefslogtreecommitdiffhomepage
path: root/android/test
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-12-28 15:45:14 +0100
committerAlbin <albin@mullvad.net>2023-01-10 15:32:45 +0100
commit209583159108f789b9cb8f9aa2c81d56cf16475c (patch)
treee1de1c69ae68a5497e388d142cd84bd76a6df469 /android/test
parent88fcc41c3595166db2893a70f7d77bacde4ad1cc (diff)
downloadmullvadvpn-209583159108f789b9cb8f9aa2c81d56cf16475c.tar.xz
mullvadvpn-209583159108f789b9cb8f9aa2c81d56cf16475c.zip
Add mockapi login tests
Diffstat (limited to 'android/test')
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/Extensions.kt26
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/JsonUtils.kt41
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt70
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt130
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt80
-rw-r--r--android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/constant/Constants.kt14
6 files changed, 361 insertions, 0 deletions
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/Extensions.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/Extensions.kt
new file mode 100644
index 0000000000..60bd0e293a
--- /dev/null
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/Extensions.kt
@@ -0,0 +1,26 @@
+package net.mullvad.mullvadvpn.test.mockapi
+
+import okhttp3.mockwebserver.MockResponse
+import okio.Buffer
+import org.json.JSONException
+import org.json.JSONObject
+
+fun MockResponse.addJsonHeader(): MockResponse {
+ return addHeader("Content-Type", "application/json")
+}
+
+fun Buffer.getAccountToken(): String? {
+ return try {
+ JSONObject(readUtf8()).getString("account_number")
+ } catch (ex: JSONException) {
+ null
+ }
+}
+
+fun Buffer.getPubKey(): String? {
+ return try {
+ JSONObject(readUtf8()).getString("pubkey")
+ } catch (ex: JSONException) {
+ null
+ }
+}
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/JsonUtils.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/JsonUtils.kt
new file mode 100644
index 0000000000..d6ee2bc6cb
--- /dev/null
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/JsonUtils.kt
@@ -0,0 +1,41 @@
+package net.mullvad.mullvadvpn.test.mockapi
+
+import java.time.OffsetDateTime
+import org.json.JSONArray
+import org.json.JSONObject
+
+fun accountInfoJson(
+ id: String,
+ expiry: OffsetDateTime
+) = JSONObject().apply {
+ put("id", id)
+ put("expiry", expiry.toString())
+ put("max_ports", 5)
+ put("can_add_ports", true)
+ put("max_devices", 5)
+ put("can_add_devices", true)
+}
+
+fun deviceJson(
+ id: String,
+ name: String,
+ publicKey: String,
+ creationDate: OffsetDateTime
+) = JSONObject().apply {
+ put("id", id)
+ put("name", name)
+ put("pubkey", publicKey)
+ put("hijack_dns", true)
+ put("created", creationDate.toString())
+ put("ipv4_address", "127.0.0.1/32")
+ put("ipv6_address", "fc00::1/128")
+ put("ports", JSONArray())
+}
+
+fun accessTokenJsonResponse(
+ accessToken: String,
+ expiry: OffsetDateTime
+) = JSONObject().apply {
+ put("access_token", accessToken)
+ put("expiry", expiry.toString())
+}
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt
new file mode 100644
index 0000000000..2a72b41373
--- /dev/null
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/LoginMockApiTest.kt
@@ -0,0 +1,70 @@
+package net.mullvad.mullvadvpn.test.mockapi
+
+import androidx.test.runner.AndroidJUnit4
+import androidx.test.uiautomator.By
+import java.time.OffsetDateTime
+import java.time.temporal.ChronoUnit
+import net.mullvad.mullvadvpn.test.common.extension.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove
+import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class LoginMockApiTest : MockApiTest() {
+ @Test
+ fun testLoginWithInvalidCredentials() {
+ // Arrange
+ val validAccountToken = "1234123412341234"
+ apiDispatcher.apply {
+ expectedAccountToken = null
+ accountExpiry =
+ OffsetDateTime.now().plusDays(1).truncatedTo(ChronoUnit.SECONDS)
+ }
+ app.launch(endpoint)
+
+ // Act
+ device.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove()
+ app.attemptLogin(validAccountToken)
+
+ // Assert
+ device.findObjectWithTimeout(By.text("Login failed"))
+ }
+
+ @Test
+ fun testLoginWithValidCredentialsToUnexpiredAccount() {
+ // Arrange
+ val validAccountToken = "1234123412341234"
+ apiDispatcher.apply {
+ expectedAccountToken = validAccountToken
+ accountExpiry =
+ OffsetDateTime.now().plusDays(1).truncatedTo(ChronoUnit.SECONDS)
+ }
+
+ // Act
+ app.launch(endpoint)
+ device.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove()
+ app.attemptLogin(validAccountToken)
+
+ // Assert
+ device.findObjectWithTimeout(By.text("UNSECURED CONNECTION"))
+ }
+
+ @Test
+ fun testLoginWithValidCredentialsToExpiredAccount() {
+ // Arrange
+ val validAccountToken = "1234123412341234"
+ apiDispatcher.apply {
+ expectedAccountToken = validAccountToken
+ accountExpiry =
+ OffsetDateTime.now().minusDays(1).truncatedTo(ChronoUnit.SECONDS)
+ }
+
+ // Act
+ app.launch(endpoint)
+ device.clickAllowOnNotificationPermissionPromptIfApiLevel31AndAbove()
+ app.attemptLogin(validAccountToken)
+
+ // Assert
+ device.findObjectWithTimeout(By.text("Out of time"))
+ }
+}
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt
new file mode 100644
index 0000000000..b41ad34fa6
--- /dev/null
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiDispatcher.kt
@@ -0,0 +1,130 @@
+package net.mullvad.mullvadvpn.test.mockapi
+
+import android.util.Log
+import java.time.OffsetDateTime
+import java.time.temporal.ChronoUnit
+import net.mullvad.mullvadvpn.test.mockapi.constant.ACCOUNT_URL_PATH
+import net.mullvad.mullvadvpn.test.mockapi.constant.AUTH_TOKEN_URL_PATH
+import net.mullvad.mullvadvpn.test.mockapi.constant.DEVICES_URL_PATH
+import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ACCESS_TOKEN
+import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_DEVICE_NAME
+import net.mullvad.mullvadvpn.test.mockapi.constant.DUMMY_ID
+import okhttp3.mockwebserver.Dispatcher
+import okhttp3.mockwebserver.MockResponse
+import okhttp3.mockwebserver.RecordedRequest
+import okio.Buffer
+import org.json.JSONArray
+
+class MockApiDispatcher : Dispatcher() {
+
+ var expectedAccountToken: String? = null
+ var accountExpiry: OffsetDateTime? = null
+
+ private var cachedPubKeyFromAppUnderTest: String? = null
+
+ override fun dispatch(request: RecordedRequest): MockResponse {
+ Log.d("mullvad", "Request: $request")
+ return when (request.path) {
+ AUTH_TOKEN_URL_PATH -> handleLoginRequest(request.body)
+ DEVICES_URL_PATH -> {
+ when (request.method) {
+ "get", "GET" -> handleDeviceListRequest()
+ "post", "POST" -> handleDeviceCreationRequest(request.body)
+ else -> MockResponse().setResponseCode(404)
+ }
+ }
+ "$DEVICES_URL_PATH/$DUMMY_ID" -> handleDeviceInfoRequest()
+ ACCOUNT_URL_PATH -> handleAccountInfoRequest()
+ else -> MockResponse().setResponseCode(404)
+ }
+ }
+
+ private fun handleLoginRequest(requestBody: Buffer): MockResponse {
+ val accountToken = requestBody.getAccountToken()
+
+ return if (accountToken != null && accountToken == expectedAccountToken) {
+ MockResponse()
+ .setResponseCode(200)
+ .addJsonHeader()
+ .setBody(
+ accessTokenJsonResponse(
+ accessToken = DUMMY_ACCESS_TOKEN,
+ expiry = OffsetDateTime.now().plusDays(1).truncatedTo(ChronoUnit.SECONDS)
+ ).toString()
+ )
+ } else {
+ MockResponse().setResponseCode(400)
+ }
+ }
+
+ private fun handleAccountInfoRequest(): MockResponse {
+ return accountExpiry?.let { expiry ->
+ MockResponse()
+ .setResponseCode(200)
+ .addJsonHeader()
+ .setBody(
+ accountInfoJson(
+ id = DUMMY_ID,
+ expiry = expiry
+ ).toString()
+ )
+ } ?: MockResponse().setResponseCode(400)
+ }
+
+ private fun handleDeviceInfoRequest(): MockResponse {
+ return cachedPubKeyFromAppUnderTest?.let { cachedKey ->
+ MockResponse()
+ .setResponseCode(200)
+ .addJsonHeader()
+ .setBody(
+ deviceJson(
+ id = DUMMY_ID,
+ name = DUMMY_DEVICE_NAME,
+ publicKey = cachedKey,
+ creationDate = OffsetDateTime.now().minusDays(1)
+ .truncatedTo(ChronoUnit.SECONDS)
+ ).toString()
+ )
+ } ?: MockResponse().setResponseCode(400)
+ }
+
+ private fun handleDeviceCreationRequest(body: Buffer): MockResponse {
+ return body.getPubKey()
+ .also { newKey ->
+ cachedPubKeyFromAppUnderTest = newKey
+ }
+ ?.let { newKey ->
+ MockResponse()
+ .setResponseCode(201)
+ .addJsonHeader()
+ .setBody(
+ deviceJson(
+ id = DUMMY_ID,
+ name = DUMMY_DEVICE_NAME,
+ publicKey = newKey,
+ creationDate = OffsetDateTime.now().minusDays(1)
+ .truncatedTo(ChronoUnit.SECONDS)
+ ).toString()
+ )
+ } ?: MockResponse().setResponseCode(400)
+ }
+
+ private fun handleDeviceListRequest(): MockResponse {
+ return cachedPubKeyFromAppUnderTest?.let { cachedKey ->
+ MockResponse()
+ .setResponseCode(200)
+ .addJsonHeader()
+ .setBody(
+ JSONArray().put(
+ deviceJson(
+ id = DUMMY_ID,
+ name = DUMMY_DEVICE_NAME,
+ publicKey = cachedKey,
+ creationDate = OffsetDateTime.now().minusDays(1)
+ .truncatedTo(ChronoUnit.SECONDS)
+ )
+ ).toString()
+ )
+ } ?: MockResponse().setResponseCode(400)
+ }
+}
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt
new file mode 100644
index 0000000000..ca4338bb2e
--- /dev/null
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/MockApiTest.kt
@@ -0,0 +1,80 @@
+package net.mullvad.mullvadvpn.test.mockapi
+
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+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 java.net.InetAddress
+import java.net.InetSocketAddress
+import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpoint
+import net.mullvad.mullvadvpn.lib.endpoint.CustomApiEndpointConfiguration
+import net.mullvad.mullvadvpn.test.common.interactor.AppInteractor
+import net.mullvad.mullvadvpn.test.common.rule.CaptureScreenshotOnFailedTestRule
+import net.mullvad.mullvadvpn.test.mockapi.constant.LOG_TAG
+import net.mullvad.mullvadvpn.test.mockapi.constant.MOCK_SERVER_LOCALHOST_ADDRESS
+import okhttp3.mockwebserver.MockWebServer
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+abstract class MockApiTest {
+
+ @Rule
+ @JvmField
+ val rule = CaptureScreenshotOnFailedTestRule(LOG_TAG)
+
+ @Rule
+ @JvmField
+ val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+ WRITE_EXTERNAL_STORAGE,
+ READ_EXTERNAL_STORAGE
+ )
+
+ protected val apiDispatcher = MockApiDispatcher()
+ private val mockWebServer = MockWebServer().apply {
+ dispatcher = apiDispatcher
+ }
+
+ lateinit var device: UiDevice
+ lateinit var targetContext: Context
+ lateinit var app: AppInteractor
+ lateinit var endpoint: CustomApiEndpointConfiguration
+
+ @Before
+ open fun setup() {
+ device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+ targetContext = InstrumentationRegistry.getInstrumentation().targetContext
+
+ app = AppInteractor(
+ device,
+ targetContext
+ )
+
+ mockWebServer.start()
+ endpoint = createEndpoint(mockWebServer.port)
+ }
+
+ @After
+ open fun teardown() {
+ mockWebServer.shutdown()
+ }
+
+ private fun createEndpoint(port: Int): CustomApiEndpointConfiguration {
+ val mockApiSocket = InetSocketAddress(
+ InetAddress.getByName(MOCK_SERVER_LOCALHOST_ADDRESS),
+ port
+ )
+ val api = ApiEndpoint(
+ address = mockApiSocket,
+ disableAddressCache = true,
+ disableTls = true,
+ forceDirectConnection = true
+ )
+ return CustomApiEndpointConfiguration(api)
+ }
+}
diff --git a/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/constant/Constants.kt b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/constant/Constants.kt
new file mode 100644
index 0000000000..713a712bf4
--- /dev/null
+++ b/android/test/mockapi/src/main/kotlin/net/mullvad/mullvadvpn/test/mockapi/constant/Constants.kt
@@ -0,0 +1,14 @@
+package net.mullvad.mullvadvpn.test.mockapi.constant
+
+const val LOG_TAG = "mullvad-mockapi"
+
+const val MOCK_SERVER_LOCALHOST_ADDRESS = "127.0.0.1"
+
+const val AUTH_TOKEN_URL_PATH = "/auth/v1/token"
+const val DEVICES_URL_PATH = "/accounts/v1/devices"
+const val ACCOUNT_URL_PATH = "/accounts/v1/accounts/me"
+
+const val DUMMY_ID = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
+const val DUMMY_DEVICE_NAME = "mole mole"
+const val DUMMY_ACCESS_TOKEN =
+ "mva_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"