mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-04 20:01:55 +00:00
Fix outgoing session verification stuck with incomplete recovery setup (#7433)
This commit is contained in:
+6
-3
@@ -15,7 +15,6 @@ import com.freeletics.flowredux.dsl.FlowReduxStateMachine
|
|||||||
import io.element.android.features.verifysession.impl.util.andLogStateChange
|
import io.element.android.features.verifysession.impl.util.andLogStateChange
|
||||||
import io.element.android.features.verifysession.impl.util.logReceivedEvents
|
import io.element.android.features.verifysession.impl.util.logReceivedEvents
|
||||||
import io.element.android.libraries.core.bool.orFalse
|
import io.element.android.libraries.core.bool.orFalse
|
||||||
import io.element.android.libraries.core.data.tryOrNull
|
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||||
@@ -23,6 +22,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS
|
|||||||
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
import io.element.android.libraries.matrix.api.verification.VerificationRequest
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.TimeoutCancellationException
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.timeout
|
import kotlinx.coroutines.flow.timeout
|
||||||
@@ -95,10 +95,13 @@ class OutgoingVerificationStateMachine(
|
|||||||
// If a key backup exists, wait until it's restored or a timeout happens
|
// If a key backup exists, wait until it's restored or a timeout happens
|
||||||
val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse()
|
val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse()
|
||||||
if (hasBackup) {
|
if (hasBackup) {
|
||||||
tryOrNull {
|
try {
|
||||||
encryptionService.recoveryStateStateFlow.filter { it == RecoveryState.ENABLED }
|
encryptionService.recoveryStateStateFlow
|
||||||
|
.filter { it == RecoveryState.ENABLED || it == RecoveryState.DISABLED || it == RecoveryState.INCOMPLETE }
|
||||||
.timeout(10.seconds)
|
.timeout(10.seconds)
|
||||||
.first()
|
.first()
|
||||||
|
} catch (_: TimeoutCancellationException) {
|
||||||
|
// The backup was not restored in time, proceed to the Completed state anyway
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.override { State.Completed.andLogStateChange() }
|
state.override { State.Completed.andLogStateChange() }
|
||||||
|
|||||||
+80
@@ -16,6 +16,7 @@ import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificat
|
|||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||||
|
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
@@ -216,6 +217,85 @@ class OutgoingVerificationPresenterTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - When verification is approved and a backup exists but recovery is disabled, the flow completes`() = runTest {
|
||||||
|
val service = unverifiedSessionService(
|
||||||
|
requestUserVerificationLambda = { },
|
||||||
|
startSasVerificationLambda = { },
|
||||||
|
approveVerificationLambda = { },
|
||||||
|
)
|
||||||
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
|
givenDoesBackupExistOnServerResult(Result.success(true))
|
||||||
|
recoveryStateStateFlow.value = RecoveryState.DISABLED
|
||||||
|
}
|
||||||
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
|
service = service,
|
||||||
|
verificationRequest = anOutgoingUserVerificationRequest(),
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
|
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
|
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
|
||||||
|
service.emitVerificationFlowState(VerificationFlowState.DidFinish)
|
||||||
|
assertThat(awaitItem().step).isEqualTo(Step.Completed)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - When verification is approved and a backup exists but recovery is incomplete, the flow completes`() = runTest {
|
||||||
|
val service = unverifiedSessionService(
|
||||||
|
requestUserVerificationLambda = { },
|
||||||
|
startSasVerificationLambda = { },
|
||||||
|
approveVerificationLambda = { },
|
||||||
|
)
|
||||||
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
|
givenDoesBackupExistOnServerResult(Result.success(true))
|
||||||
|
recoveryStateStateFlow.value = RecoveryState.INCOMPLETE
|
||||||
|
}
|
||||||
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
|
service = service,
|
||||||
|
verificationRequest = anOutgoingUserVerificationRequest(),
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
|
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
|
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
|
||||||
|
service.emitVerificationFlowState(VerificationFlowState.DidFinish)
|
||||||
|
assertThat(awaitItem().step).isEqualTo(Step.Completed)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - When verification is approved but the recovery state is not determined, the flow completes after the timeout`() = runTest {
|
||||||
|
val service = unverifiedSessionService(
|
||||||
|
requestUserVerificationLambda = { },
|
||||||
|
startSasVerificationLambda = { },
|
||||||
|
approveVerificationLambda = { },
|
||||||
|
)
|
||||||
|
val encryptionService = FakeEncryptionService().apply {
|
||||||
|
givenDoesBackupExistOnServerResult(Result.success(true))
|
||||||
|
}
|
||||||
|
val presenter = createOutgoingVerificationPresenter(
|
||||||
|
service = service,
|
||||||
|
verificationRequest = anOutgoingUserVerificationRequest(),
|
||||||
|
encryptionService = encryptionService,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||||
|
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
|
||||||
|
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
|
||||||
|
service.emitVerificationFlowState(VerificationFlowState.DidFinish)
|
||||||
|
// The recovery state never leaves UNKNOWN, so the 10 seconds timeout fires and the flow completes anyway
|
||||||
|
this@runTest.advanceUntilIdle()
|
||||||
|
assertThat(awaitItem().step).isEqualTo(Step.Completed)
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - When verification is declined, the flow is canceled`() = runTest {
|
fun `present - When verification is declined, the flow is canceled`() = runTest {
|
||||||
val service = unverifiedSessionService(
|
val service = unverifiedSessionService(
|
||||||
|
|||||||
Reference in New Issue
Block a user