Added Self verification UI test

This commit is contained in:
valere 2023-01-03 19:35:15 +01:00
parent 8eda089edc
commit 419673675c
4 changed files with 120 additions and 88 deletions

View File

@ -194,7 +194,7 @@ class E2eeSanityTests : InstrumentedTest {
val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null) val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo) val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
Log.v("#E2E TEST", "... Key backup started and enabled for bob") Log.v("#E2E TEST", "... Key backup started and enabled for bob: version:$version")
// Bob session should now have // Bob session should now have
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
@ -230,6 +230,7 @@ class E2eeSanityTests : InstrumentedTest {
fail("All keys should be backedup") fail("All keys should be backedup")
} }
) { ) {
Log.v("#E2E TEST", "backedUp=${ bobKeysBackupService.getTotalNumbersOfBackedUpKeys()}, known=${bobKeysBackupService.getTotalNumbersOfKeys()}")
bobKeysBackupService.getTotalNumbersOfBackedUpKeys() == bobKeysBackupService.getTotalNumbersOfKeys() bobKeysBackupService.getTotalNumbersOfBackedUpKeys() == bobKeysBackupService.getTotalNumbersOfKeys()
} }
Log.v("#E2E TEST", "... Key backup done for Bob") Log.v("#E2E TEST", "... Key backup done for Bob")
@ -268,7 +269,7 @@ class E2eeSanityTests : InstrumentedTest {
) // MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) ) // MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Let's now import keys from backup // Let's now import keys from backup
Log.v("#E2E TEST", "Restore backup for the new session")
newBobSession.cryptoService().keysBackupService().let { kbs -> newBobSession.cryptoService().keysBackupService().let { kbs ->
val keyVersionResult = kbs.getVersion(version.version) val keyVersionResult = kbs.getVersion(version.version)
@ -284,9 +285,12 @@ class E2eeSanityTests : InstrumentedTest {
} }
// ensure bob can now decrypt // ensure bob can now decrypt
Log.v("#E2E TEST", "Check that bob can decrypt now")
cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText) cryptoTestHelper.ensureCanDecrypt(sentEventIds, newBobSession, e2eRoomID, messagesText)
// Check key trust // Check key trust
Log.v("#E2E TEST", "Check key safety")
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!! val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)!!
val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "") val result = newBobSession.cryptoService().decryptEvent(timelineEvent.root, "")

View File

@ -296,6 +296,8 @@ internal abstract class SessionModule {
var uri = sessionParams.homeServerConnectionConfig.homeServerUriBase.toString() var uri = sessionParams.homeServerConnectionConfig.homeServerUriBase.toString()
if (uri == "http://localhost:8080") { if (uri == "http://localhost:8080") {
uri = "http://10.0.2.2:8080" uri = "http://10.0.2.2:8080"
} else if (uri == "http://localhost:8081") {
uri = "http://10.0.2.2:8081"
} }
return retrofitFactory return retrofitFactory
.create(okHttpClient, uri) .create(okHttpClient, uri)

View File

@ -18,6 +18,7 @@ package im.vector.app
import android.net.Uri import android.net.Uri
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import im.vector.app.ui.robot.ElementRobot
import im.vector.app.ui.robot.OnboardingRobot import im.vector.app.ui.robot.OnboardingRobot
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -41,6 +42,7 @@ abstract class VerificationTestBase {
val homeServerUrl: String = "http://10.0.2.2:8080" val homeServerUrl: String = "http://10.0.2.2:8080"
protected val uiTestBase = OnboardingRobot() protected val uiTestBase = OnboardingRobot()
protected val elementRobot = ElementRobot()
fun createAccountAndSync( fun createAccountAndSync(
matrix: Matrix, matrix: Matrix,
@ -114,9 +116,10 @@ abstract class VerificationTestBase {
private fun syncSession(session: Session) { private fun syncSession(session: Session) {
val lock = CountDownLatch(1) val lock = CountDownLatch(1)
GlobalScope.launch(Dispatchers.Main) { session.open() } GlobalScope.launch(Dispatchers.Main) {
session.open()
session.syncService().startSync(true) session.syncService().startSync(true)
}
val syncLiveData = runBlocking(Dispatchers.Main) { val syncLiveData = runBlocking(Dispatchers.Main) {
session.syncService().getSyncStateLive() session.syncService().getSyncStateLive()

View File

@ -19,10 +19,8 @@ package im.vector.app
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource import androidx.test.espresso.IdlingResource
import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.closeSoftKeyboard
import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem import androidx.test.espresso.contrib.RecyclerViewActions.actionOnItem
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
@ -33,19 +31,25 @@ import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import com.adevinta.android.barista.internal.viewaction.SleepViewAction
import im.vector.app.core.utils.getMatrixInstance import im.vector.app.core.utils.getMatrixInstance
import im.vector.app.espresso.tools.waitUntilActivityVisible
import im.vector.app.espresso.tools.waitUntilViewVisible
import im.vector.app.features.MainActivity import im.vector.app.features.MainActivity
import im.vector.app.features.home.HomeActivity import im.vector.app.features.home.HomeActivity
import im.vector.app.ui.robot.AnalyticsRobot
import im.vector.app.ui.robot.ElementRobot
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import org.amshove.kluent.internal.assertEquals
import org.hamcrest.CoreMatchers.not import org.hamcrest.CoreMatchers.not
import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import org.junit.Ignore
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -54,9 +58,11 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState import org.matrix.android.sdk.api.session.crypto.verification.SasTransactionState
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.getRequest
import org.matrix.android.sdk.api.session.crypto.verification.getTransaction import org.matrix.android.sdk.api.session.crypto.verification.getTransaction
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -64,7 +70,6 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@LargeTest @LargeTest
@Ignore
class VerifySessionInteractiveTest : VerificationTestBase() { class VerifySessionInteractiveTest : VerificationTestBase() {
var existingSession: Session? = null var existingSession: Session? = null
@ -72,6 +77,8 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
@get:Rule @get:Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java) val activityRule = ActivityScenarioRule(MainActivity::class.java)
private val testScope = CoroutineScope(SupervisorJob())
@Before @Before
fun createSessionWithCrossSigning() { fun createSessionWithCrossSigning() {
val matrix = getMatrixInstance() val matrix = getMatrixInstance()
@ -101,28 +108,23 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl) uiTestBase.login(userId = userId, password = password, homeServerUrl = homeServerUrl)
// Thread.sleep(6000) val analyticsRobot = AnalyticsRobot()
withIdlingResource(activityIdlingResource(HomeActivity::class.java)) { analyticsRobot.optOut()
onView(withId(R.id.roomListContainer))
.check(matches(isDisplayed()))
.perform(closeSoftKeyboard())
}
waitUntilActivityVisible<HomeActivity> {
waitUntilViewVisible(withId(R.id.roomListContainer))
}
val activity = EspressoHelper.getCurrentActivity()!! val activity = EspressoHelper.getCurrentActivity()!!
val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession() val uiSession = (activity as HomeActivity).activeSessionHolder.getActiveSession()
withIdlingResource(initialSyncIdlingResource(uiSession)) { withIdlingResource(initialSyncIdlingResource(uiSession)) {
onView(withId(R.id.roomListContainer)) waitUntilViewVisible(withId(R.id.roomListContainer))
.check(matches(isDisplayed()))
} }
// THIS IS THE ONLY WAY I FOUND TO CLICK ON ALERTERS... :( // THIS IS THE ONLY WAY I FOUND TO CLICK ON ALERTERS... :(
// Cannot wait for view because of alerter animation? ... // Cannot wait for view because of alerter animation? ...
onView(isRoot()) onView(isRoot())
.perform(waitForView(withId(com.tapadoo.alerter.R.id.llAlertBackground))) .perform(waitForView(withId(com.tapadoo.alerter.R.id.llAlertBackground)))
// Thread.sleep(1000)
// onView(withId(com.tapadoo.alerter.R.id.llAlertBackground))
// .perform(click())
Thread.sleep(1000) Thread.sleep(1000)
val popup = activity.findViewById<View>(com.tapadoo.alerter.R.id.llAlertBackground) val popup = activity.findViewById<View>(com.tapadoo.alerter.R.id.llAlertBackground)
activity.runOnUiThread { activity.runOnUiThread {
@ -131,74 +133,105 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
onView(isRoot()) onView(isRoot())
.perform(waitForView(withId(R.id.bottomSheetFragmentContainer))) .perform(waitForView(withId(R.id.bottomSheetFragmentContainer)))
// .check()
// onView(withId(R.id.bottomSheetFragmentContainer))
// .check(matches(isDisplayed()))
// onView(isRoot()).perform(SleepViewAction.sleep(2000)) onView(withText(R.string.verification_verify_identity))
onView(withText(R.string.use_latest_app))
.check(matches(isDisplayed())) .check(matches(isDisplayed()))
// 4S is not setup so passphrase option should be hidden // 4S is not setup so passphrase option should be hidden
onView(withId(R.id.bottomSheetFragmentContainer)) onView(withId(R.id.bottomSheetVerificationRecyclerView))
.check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session))))) .check(matches(not(hasDescendant(withText(R.string.verification_cannot_access_other_session)))))
val request = runBlockingTest { onView(withId(R.id.bottomSheetVerificationRecyclerView))
existingSession!!.cryptoService().verificationService().requestSelfKeyVerification( .check(matches(hasDescendant(withText(R.string.verification_verify_with_another_device))))
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
) onView(withId(R.id.bottomSheetVerificationRecyclerView))
.check(matches(hasDescendant(withText(R.string.bad_passphrase_key_reset_all_action))))
val otherRequest = CompletableDeferred<PendingVerificationRequest>()
testScope.launch {
existingSession!!.cryptoService().verificationService().requestEventFlow().collect {
if (it.getRequest() != null) {
otherRequest.complete(it.getRequest()!!)
return@collect cancel()
}
}
} }
val transactionId = request.transactionId // Send out a self verification request
val sasReadyIdle = verificationStateIdleResource(transactionId, SasTransactionState.SasShortCodeReady, uiSession)
val otherSessionSasReadyIdle = verificationStateIdleResource(transactionId, SasTransactionState.SasShortCodeReady, existingSession!!)
onView(isRoot()).perform(SleepViewAction.sleep(1000))
// Assert QR code option is there and available
onView(withId(R.id.bottomSheetVerificationRecyclerView))
.check(matches(hasDescendant(withText(R.string.verification_scan_their_code))))
onView(withId(R.id.bottomSheetVerificationRecyclerView))
.check(matches(hasDescendant(withId(R.id.itemVerificationQrCodeImage))))
onView(withId(R.id.bottomSheetVerificationRecyclerView)) onView(withId(R.id.bottomSheetVerificationRecyclerView))
.perform( .perform(
actionOnItem<RecyclerView.ViewHolder>( actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText(R.string.verification_scan_emoji_title)), hasDescendant(withText(R.string.verification_verify_with_another_device)),
click() click()
) )
) )
val firstSessionTr = runBlockingTest { onView(withId(R.id.bottomSheetVerificationRecyclerView))
existingSession!!.cryptoService().verificationService().getExistingTransaction( .check(matches(hasDescendant(withText(R.string.verification_request_was_sent))))
val txId = runBlockingTest {
otherRequest.await().transactionId
}
// accept from other session
runBlockingTest {
existingSession!!.cryptoService().verificationService().readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
existingSession!!.myUserId, existingSession!!.myUserId,
transactionId txId
) as SasVerificationTransaction )
} }
IdlingRegistry.getInstance().register(sasReadyIdle) onView(withId(R.id.bottomSheetVerificationRecyclerView))
IdlingRegistry.getInstance().register(otherSessionSasReadyIdle) .perform(waitForView(hasDescendant(withText(R.string.verification_scan_self_notice))))
onView(isRoot()).perform(SleepViewAction.sleep(300)) onView(withId(R.id.bottomSheetVerificationRecyclerView))
// will only execute when Idle is ready .perform(waitForView(hasDescendant(withText(R.string.verification_scan_self_emoji_subtitle))))
val expectedEmojis = firstSessionTr.getEmojiCodeRepresentation()
val targets = listOf(R.id.emoji0, R.id.emoji1, R.id.emoji2, R.id.emoji3, R.id.emoji4, R.id.emoji5, R.id.emoji6) // there should be the QR code also
targets.forEachIndexed { index, res -> onView(withId(R.id.bottomSheetVerificationRecyclerView))
onView(withId(res)) .check(matches(hasDescendant(withId(R.id.itemVerificationQrCodeImage))))
.check(
matches(hasDescendant(withText(expectedEmojis[index].nameResId))) // proceed with emoji
) onView(withId(R.id.bottomSheetVerificationRecyclerView))
.perform(
actionOnItem<RecyclerView.ViewHolder>(
hasDescendant(withText(R.string.verification_scan_self_emoji_subtitle)),
click()
)
)
onView(withId(R.id.bottomSheetVerificationRecyclerView))
.perform(waitForView(hasDescendant(withText(R.string.verification_sas_do_not_match))))
onView(withId(R.id.bottomSheetVerificationRecyclerView))
.perform(waitForView(hasDescendant(withText(R.string.verification_sas_match))))
// check that the code matches
val uiCode = runBlockingTest {
(uiSession.cryptoService()
.verificationService()
.getExistingTransaction(uiSession.myUserId, txId) as SasVerificationTransaction)
.getDecimalCodeRepresentation()!!
} }
IdlingRegistry.getInstance().unregister(sasReadyIdle) val backgroundCode = runBlockingTest {
IdlingRegistry.getInstance().unregister(otherSessionSasReadyIdle) (existingSession!!.cryptoService()
.verificationService()
.getExistingTransaction(uiSession.myUserId, txId) as SasVerificationTransaction)
.getDecimalCodeRepresentation()!!
}
val verificationSuccessIdle = assertEquals("SAS code should be equals", backgroundCode, uiCode)
verificationStateIdleResource(transactionId, SasTransactionState.Done(true), uiSession)
// CLICK ON THEY MATCH runBlockingTest {
(existingSession!!.cryptoService()
.verificationService()
.getExistingTransaction(uiSession.myUserId, txId) as SasVerificationTransaction)
.userHasVerifiedShortCode()
}
// Do the same on ui
onView(withId(R.id.bottomSheetVerificationRecyclerView)) onView(withId(R.id.bottomSheetVerificationRecyclerView))
.perform( .perform(
actionOnItem<RecyclerView.ViewHolder>( actionOnItem<RecyclerView.ViewHolder>(
@ -207,23 +240,10 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
) )
) )
runBlockingTest { onView(withId(R.id.bottomSheetVerificationRecyclerView))
firstSessionTr.userHasVerifiedShortCode() .perform(waitForView(hasDescendant(withText(R.string.verification_conclusion_ok_notice))))
}
onView(isRoot()).perform(SleepViewAction.sleep(1000)) // click on done
withIdlingResource(verificationSuccessIdle) {
onView(withId(R.id.bottomSheetVerificationRecyclerView))
.check(
matches(hasDescendant(withText(R.string.verification_conclusion_ok_self_notice)))
)
}
// Wait a bit before done (to delay a bit sending of secrets to let other have time
// to mark as verified :/
Thread.sleep(5_000)
// Click on done
onView(withId(R.id.bottomSheetVerificationRecyclerView)) onView(withId(R.id.bottomSheetVerificationRecyclerView))
.perform( .perform(
actionOnItem<RecyclerView.ViewHolder>( actionOnItem<RecyclerView.ViewHolder>(
@ -232,11 +252,14 @@ class VerifySessionInteractiveTest : VerificationTestBase() {
) )
) )
// Wait until local secrets are known (gossip) Thread.sleep(1_000)
withIdlingResource(allSecretsKnownIdling(uiSession)) {
onView(withId(R.id.roomListContainer)) // check that current session is actually trusted
.check(matches(isDisplayed())) assertTrue("I should be verified",
} runBlockingTest { uiSession.cryptoService().crossSigningService().isCrossSigningVerified() }
)
ElementRobot().signout(false)
} }
fun signout() { fun signout() {