mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 16:22:49 +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.logReceivedEvents
|
||||
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.RecoveryState
|
||||
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 kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.first
|
||||
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
|
||||
val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse()
|
||||
if (hasBackup) {
|
||||
tryOrNull {
|
||||
encryptionService.recoveryStateStateFlow.filter { it == RecoveryState.ENABLED }
|
||||
try {
|
||||
encryptionService.recoveryStateStateFlow
|
||||
.filter { it == RecoveryState.ENABLED || it == RecoveryState.DISABLED || it == RecoveryState.INCOMPLETE }
|
||||
.timeout(10.seconds)
|
||||
.first()
|
||||
} catch (_: TimeoutCancellationException) {
|
||||
// The backup was not restored in time, proceed to the Completed state anyway
|
||||
}
|
||||
}
|
||||
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.matrix.api.core.UserId
|
||||
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.SessionVerificationService
|
||||
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
|
||||
fun `present - When verification is declined, the flow is canceled`() = runTest {
|
||||
val service = unverifiedSessionService(
|
||||
|
||||
Reference in New Issue
Block a user