diff options
| author | Albin <albin@mullvad.net> | 2022-12-28 15:45:14 +0100 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-01-10 15:32:45 +0100 |
| commit | 209583159108f789b9cb8f9aa2c81d56cf16475c (patch) | |
| tree | e1de1c69ae68a5497e388d142cd84bd76a6df469 /android/test/mockapi/src/main | |
| parent | 88fcc41c3595166db2893a70f7d77bacde4ad1cc (diff) | |
| download | mullvadvpn-209583159108f789b9cb8f9aa2c81d56cf16475c.tar.xz mullvadvpn-209583159108f789b9cb8f9aa2c81d56cf16475c.zip | |
Add mockapi login tests
Diffstat (limited to 'android/test/mockapi/src/main')
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" |
