Merge pull request #7457 from hayaksi1/fix/1270-verification-request-timeout

Stop waiting on a verification request the other session never accepts
This commit is contained in:
Benoit Marty
2026-08-25 14:22:39 +02:00
committed by GitHub
2 changed files with 37 additions and 4 deletions
@@ -23,9 +23,11 @@ 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.TimeoutCancellationException
import kotlinx.coroutines.delay
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
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
import com.freeletics.flowredux.dsl.State as MachineState import com.freeletics.flowredux.dsl.State as MachineState
@@ -36,6 +38,11 @@ class OutgoingVerificationStateMachine(
) : FlowReduxStateMachine<OutgoingVerificationStateMachine.State, OutgoingVerificationStateMachine.Event>( ) : FlowReduxStateMachine<OutgoingVerificationStateMachine.State, OutgoingVerificationStateMachine.Event>(
initialState = State.Initial, initialState = State.Initial,
) { ) {
companion object {
/** Lifetime the Matrix specification gives a verification request; past it the other session can no longer accept ours. */
private val VERIFICATION_REQUEST_TIMEOUT = 10.minutes
}
init { init {
spec { spec {
inState<State.Initial> { inState<State.Initial> {
@@ -44,11 +51,15 @@ class OutgoingVerificationStateMachine(
} }
} }
inState<State.RequestingVerification> { inState<State.RequestingVerification> {
onEnterEffect { event -> onEnter { state ->
when (event.verificationRequest) { val verificationRequest = state.snapshot.verificationRequest
when (verificationRequest) {
is VerificationRequest.Outgoing.CurrentSession -> sessionVerificationService.requestDeviceVerification() is VerificationRequest.Outgoing.CurrentSession -> sessionVerificationService.requestDeviceVerification()
is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(event.verificationRequest.userId) is VerificationRequest.Outgoing.User -> sessionVerificationService.requestUserVerification(verificationRequest.userId)
} }
delay(VERIFICATION_REQUEST_TIMEOUT)
sessionVerificationService.cancelVerification()
state.override { State.Canceled.andLogStateChange() }
} }
on<Event.DidAcceptVerificationRequest> { _, state -> on<Event.DidAcceptVerificationRequest> { _, state ->
state.override { State.VerificationRequestAccepted.andLogStateChange() } state.override { State.VerificationRequestAccepted.andLogStateChange() }
@@ -31,10 +31,12 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test import io.element.android.tests.testutils.test
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import kotlin.time.Duration.Companion.minutes
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
class OutgoingVerificationPresenterTest { class OutgoingVerificationPresenterTest {
@@ -141,6 +143,26 @@ class OutgoingVerificationPresenterTest {
} }
} }
@Test
fun `present - A verification request the other session never accepts is canceled`() = runTest {
val cancelVerificationRecorder = lambdaRecorder<Unit> {}
val service = unverifiedSessionService(
requestDeviceVerificationLambda = { },
cancelVerificationLambda = cancelVerificationRecorder,
)
val presenter = createOutgoingVerificationPresenter(service)
presenter.test {
awaitItem().eventSink(OutgoingVerificationViewEvent.RequestVerification)
advanceTimeBy(1.minutes)
assertThat(awaitItem().step).isEqualTo(Step.AwaitingOtherDeviceResponse)
advanceTimeBy(9.minutes)
expectNoEvents()
advanceTimeBy(2.minutes)
assertThat(awaitItem().step).isEqualTo(Step.Canceled)
cancelVerificationRecorder.assertions().isCalledOnce()
}
}
@Test @Test
fun `present - Canceling the flow once it's verifying cancels it`() = runTest { fun `present - Canceling the flow once it's verifying cancels it`() = runTest {
val service = unverifiedSessionService( val service = unverifiedSessionService(
@@ -363,7 +385,7 @@ class OutgoingVerificationPresenterTest {
var state = awaitItem() var state = awaitItem()
assertThat(state.step).isEqualTo(Step.Initial) assertThat(state.step).isEqualTo(Step.Initial)
state.eventSink(OutgoingVerificationViewEvent.RequestVerification) state.eventSink(OutgoingVerificationViewEvent.RequestVerification)
testScope.advanceUntilIdle() testScope.advanceTimeBy(1.minutes)
// Await for other device response: // Await for other device response:
fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest) fakeService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)
state = awaitItem() state = awaitItem()