You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
5.4 KiB
Kotlin
161 lines
5.4 KiB
Kotlin
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package com.tailscale.ipn
|
|
|
|
import android.os.Build
|
|
import android.util.Log
|
|
import android.widget.Button
|
|
import android.widget.EditText
|
|
import androidx.test.ext.junit.rules.activityScenarioRule
|
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
import androidx.test.filters.LargeTest
|
|
import androidx.test.platform.app.InstrumentationRegistry
|
|
import androidx.test.uiautomator.By
|
|
import androidx.test.uiautomator.UiDevice
|
|
import androidx.test.uiautomator.UiSelector
|
|
import dev.turingcomplete.kotlinonetimepassword.HmacAlgorithm
|
|
import dev.turingcomplete.kotlinonetimepassword.TimeBasedOneTimePasswordConfig
|
|
import dev.turingcomplete.kotlinonetimepassword.TimeBasedOneTimePasswordGenerator
|
|
import org.apache.commons.codec.binary.Base32
|
|
import org.junit.After
|
|
import org.junit.Assert
|
|
import org.junit.Before
|
|
import org.junit.Rule
|
|
import org.junit.Test
|
|
import org.junit.runner.RunWith
|
|
import java.net.HttpURLConnection
|
|
import java.net.URL
|
|
import java.util.concurrent.TimeUnit
|
|
import kotlin.time.Duration.Companion.minutes
|
|
import kotlin.time.Duration.Companion.seconds
|
|
|
|
@RunWith(AndroidJUnit4::class)
|
|
@LargeTest
|
|
class MainActivityTest {
|
|
companion object {
|
|
const val TAG = "MainActivityTest"
|
|
}
|
|
|
|
@get:Rule val activityRule = activityScenarioRule<MainActivity>()
|
|
|
|
@Before fun setUp() {}
|
|
|
|
@After fun tearDown() {}
|
|
|
|
/**
|
|
* This test starts with a clean install, logs the user in to a tailnet using credentials provided
|
|
* through a build config, and then makes sure we can hit https://hello.ts.net.
|
|
*/
|
|
@Test
|
|
fun loginAndVisitHello() {
|
|
val githubUsername = BuildConfig.GITHUB_USERNAME
|
|
val githubPassword = BuildConfig.GITHUB_PASSWORD
|
|
val github2FASecret = Base32().decode(BuildConfig.GITHUB_2FA_SECRET)
|
|
val config =
|
|
TimeBasedOneTimePasswordConfig(
|
|
codeDigits = 6,
|
|
hmacAlgorithm = HmacAlgorithm.SHA1,
|
|
timeStep = 30,
|
|
timeStepUnit = TimeUnit.SECONDS)
|
|
val githubTOTP = TimeBasedOneTimePasswordGenerator(github2FASecret, config)
|
|
|
|
val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
|
Log.d(TAG, "Wait for VPN permission prompt and accept")
|
|
device.find(By.text("Connection request"))
|
|
device.find(By.text("OK")).click()
|
|
|
|
Log.d(TAG, "Click through Get Started screen")
|
|
device.find(By.text("Get Started"))
|
|
device.find(By.text("Get Started")).click()
|
|
|
|
asNecessary(
|
|
timeout = 2.minutes,
|
|
{
|
|
Log.d(TAG, "Log in")
|
|
device.find(By.text("Log in")).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Accept Chrome terms and conditions (if necessary)")
|
|
device.find(By.text("Welcome to Chrome"))
|
|
val dismissIndex =
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 1 else 0
|
|
device.find(UiSelector().instance(dismissIndex).className(Button::class.java)).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Don't turn on sync")
|
|
device.find(By.text("Turn on sync?"))
|
|
device.find(By.text("No thanks")).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Log in with GitHub")
|
|
device.find(By.text("Sign in with GitHub")).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Make sure GitHub page has loaded")
|
|
device.find(By.text("New to GitHub"))
|
|
device.find(By.text("Username or email address"))
|
|
device.find(By.text("Sign in"))
|
|
},
|
|
{
|
|
Log.d(TAG, "Enter credentials")
|
|
device
|
|
.find(UiSelector().instance(0).className(EditText::class.java))
|
|
.setText(githubUsername)
|
|
device
|
|
.find(UiSelector().instance(1).className(EditText::class.java))
|
|
.setText(githubPassword)
|
|
device.find(By.text("Sign in")).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Enter 2FA")
|
|
device.find(By.text("Two-factor authentication"))
|
|
device
|
|
.find(UiSelector().instance(0).className(EditText::class.java))
|
|
.setText(githubTOTP.generate())
|
|
device.find(UiSelector().instance(0).className(Button::class.java)).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Accept Tailscale app")
|
|
device.find(By.text("Learn more about OAuth"))
|
|
// Sleep a little to give button time to activate
|
|
Thread.sleep(5.seconds.inWholeMilliseconds)
|
|
device.find(UiSelector().instance(1).className(Button::class.java)).click()
|
|
},
|
|
{
|
|
Log.d(TAG, "Connect device")
|
|
device.find(By.text("Connect device"))
|
|
device.find(UiSelector().instance(0).className(Button::class.java)).click()
|
|
},
|
|
)
|
|
|
|
try {
|
|
Log.d(TAG, "Accept Permission (Either Storage or Notifications)")
|
|
device.find(By.text("Continue")).click()
|
|
device.find(By.text("Allow")).click()
|
|
} catch (t: Throwable) {
|
|
// we're not always prompted for permissions, that's okay
|
|
}
|
|
|
|
Log.d(TAG, "Wait for VPN to connect")
|
|
device.find(By.text("Connected"))
|
|
|
|
val helloResponse = helloTSNet
|
|
Assert.assertTrue(
|
|
"Response from hello.ts.net should show success",
|
|
helloResponse.contains("You're connected over Tailscale!"))
|
|
}
|
|
}
|
|
|
|
private val helloTSNet: String
|
|
get() {
|
|
return URL("https://hello.ts.net").run {
|
|
openConnection().run {
|
|
this as HttpURLConnection
|
|
connectTimeout = 30000
|
|
readTimeout = 5000
|
|
inputStream.bufferedReader().readText()
|
|
}
|
|
}
|
|
}
|