Merge pull request #566 from vector-im/feature/fga/update-rust-sdk-0.1.16
Feature/fga/update rust sdk 0.1.16
This commit is contained in:
commit
511b26b2ab
16 changed files with 251 additions and 666 deletions
|
|
@ -39,7 +39,7 @@ dependencies {
|
|||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.elementresources)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.statemachine)
|
||||
api(libs.statemachine)
|
||||
api(projects.features.verifysession.api)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
|
|
|
|||
|
|
@ -14,24 +14,31 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.verifysession.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.freeletics.flowredux.compose.rememberStateAndDispatch
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
import io.element.android.features.verifysession.impl.VerifySelfSessionStateMachine.Event as StateMachineEvent
|
||||
import io.element.android.features.verifysession.impl.VerifySelfSessionStateMachine.State as StateMachineState
|
||||
|
||||
class VerifySelfSessionPresenter @Inject constructor(
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val stateMachine: VerifySelfSessionStateMachine,
|
||||
) : Presenter<VerifySelfSessionState> {
|
||||
|
||||
@Composable
|
||||
|
|
@ -40,48 +47,36 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
// Force reset, just in case the service was left in a broken state
|
||||
sessionVerificationService.reset()
|
||||
}
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val stateMachine = remember { VerifySelfSessionStateMachine(coroutineScope, sessionVerificationService) }
|
||||
|
||||
// Create the new view state from the StateMachine state
|
||||
val stateMachineCurrentState by stateMachine.state.collectAsState()
|
||||
val verificationFlowState by remember {
|
||||
derivedStateOf { stateMachineStateToViewState(stateMachineCurrentState) }
|
||||
val stateAndDispatch = stateMachine.rememberStateAndDispatch()
|
||||
val verificationFlowStep by remember {
|
||||
derivedStateOf { stateAndDispatch.state.value.toVerificationStep() }
|
||||
}
|
||||
// Start this after observing state machine
|
||||
LaunchedEffect(Unit) {
|
||||
observeVerificationService()
|
||||
}
|
||||
|
||||
fun handleEvents(event: VerifySelfSessionViewEvents) {
|
||||
when (event) {
|
||||
VerifySelfSessionViewEvents.RequestVerification -> stateMachine.process(StateMachineEvent.RequestVerification)
|
||||
VerifySelfSessionViewEvents.StartSasVerification -> stateMachine.process(StateMachineEvent.StartSasVerification)
|
||||
VerifySelfSessionViewEvents.Restart -> stateMachine.process(StateMachineEvent.Restart)
|
||||
VerifySelfSessionViewEvents.ConfirmVerification -> stateMachine.process(StateMachineEvent.AcceptChallenge)
|
||||
VerifySelfSessionViewEvents.DeclineVerification -> stateMachine.process(StateMachineEvent.DeclineChallenge)
|
||||
VerifySelfSessionViewEvents.CancelAndClose -> {
|
||||
if (stateMachineCurrentState !in sequenceOf(
|
||||
StateMachineState.Initial,
|
||||
StateMachineState.Completed,
|
||||
StateMachineState.Canceled
|
||||
)
|
||||
) {
|
||||
stateMachine.process(StateMachineEvent.Cancel)
|
||||
}
|
||||
}
|
||||
VerifySelfSessionViewEvents.RequestVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.RequestVerification)
|
||||
VerifySelfSessionViewEvents.StartSasVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.StartSasVerification)
|
||||
VerifySelfSessionViewEvents.Restart -> stateAndDispatch.dispatchAction(StateMachineEvent.Restart)
|
||||
VerifySelfSessionViewEvents.ConfirmVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.AcceptChallenge)
|
||||
VerifySelfSessionViewEvents.DeclineVerification -> stateAndDispatch.dispatchAction(StateMachineEvent.DeclineChallenge)
|
||||
VerifySelfSessionViewEvents.CancelAndClose -> stateAndDispatch.dispatchAction(StateMachineEvent.Cancel)
|
||||
}
|
||||
}
|
||||
|
||||
return VerifySelfSessionState(
|
||||
verificationFlowStep = verificationFlowState,
|
||||
verificationFlowStep = verificationFlowStep,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
|
||||
private fun stateMachineStateToViewState(state: StateMachineState): VerifySelfSessionState.VerificationStep =
|
||||
when (state) {
|
||||
StateMachineState.Initial -> {
|
||||
private fun StateMachineState?.toVerificationStep(): VerifySelfSessionState.VerificationStep =
|
||||
when (val machineState = this) {
|
||||
StateMachineState.Initial, null -> {
|
||||
VerifySelfSessionState.VerificationStep.Initial
|
||||
}
|
||||
|
||||
StateMachineState.RequestingVerification,
|
||||
StateMachineState.StartingSasVerification,
|
||||
StateMachineState.SasVerificationStarted,
|
||||
|
|
@ -98,15 +93,41 @@ class VerifySelfSessionPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
is StateMachineState.Verifying -> {
|
||||
val async = when (state) {
|
||||
val async = when (machineState) {
|
||||
is StateMachineState.Verifying.Replying -> Async.Loading()
|
||||
else -> Async.Uninitialized
|
||||
}
|
||||
VerifySelfSessionState.VerificationStep.Verifying(state.emojis, async)
|
||||
VerifySelfSessionState.VerificationStep.Verifying(machineState.emojis, async)
|
||||
}
|
||||
|
||||
StateMachineState.Completed -> {
|
||||
VerifySelfSessionState.VerificationStep.Completed
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.observeVerificationService() {
|
||||
sessionVerificationService.verificationFlowState.onEach { verificationAttemptState ->
|
||||
when (verificationAttemptState) {
|
||||
VerificationFlowState.Initial -> stateMachine.dispatch(VerifySelfSessionStateMachine.Event.Restart)
|
||||
VerificationFlowState.AcceptedVerificationRequest -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidAcceptVerificationRequest)
|
||||
}
|
||||
VerificationFlowState.StartedSasVerification -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidStartSasVerification)
|
||||
}
|
||||
is VerificationFlowState.ReceivedVerificationData -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidReceiveChallenge(verificationAttemptState.emoji))
|
||||
}
|
||||
VerificationFlowState.Finished -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidAcceptChallenge)
|
||||
}
|
||||
VerificationFlowState.Canceled -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidCancel)
|
||||
}
|
||||
VerificationFlowState.Failed -> {
|
||||
stateMachine.dispatch(VerifySelfSessionStateMachine.Event.DidFail)
|
||||
}
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,102 +15,114 @@
|
|||
*/
|
||||
|
||||
@file:Suppress("WildcardImport")
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.verifysession.impl
|
||||
|
||||
import io.element.android.libraries.statemachine.createStateMachine
|
||||
import com.freeletics.flowredux.dsl.FlowReduxStateMachine
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import javax.inject.Inject
|
||||
import com.freeletics.flowredux.dsl.State as MachineState
|
||||
|
||||
class VerifySelfSessionStateMachine(
|
||||
coroutineScope: CoroutineScope,
|
||||
class VerifySelfSessionStateMachine @Inject constructor(
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
) : FlowReduxStateMachine<VerifySelfSessionStateMachine.State, VerifySelfSessionStateMachine.Event>(
|
||||
initialState = State.Initial
|
||||
) {
|
||||
|
||||
private val stateMachine = createStateMachine {
|
||||
addInitialState(State.Initial) {
|
||||
on<Event.RequestVerification>(State.RequestingVerification)
|
||||
on<Event.StartSasVerification>(State.StartingSasVerification)
|
||||
}
|
||||
addState<State.RequestingVerification> {
|
||||
onEnter { sessionVerificationService.requestVerification() }
|
||||
|
||||
on<Event.DidAcceptVerificationRequest>(State.VerificationRequestAccepted)
|
||||
on<Event.DidFail>(State.Initial)
|
||||
}
|
||||
addState<State.StartingSasVerification> {
|
||||
onEnter { sessionVerificationService.startVerification() }
|
||||
}
|
||||
addState<State.VerificationRequestAccepted> {
|
||||
on<Event.StartSasVerification>(State.StartingSasVerification)
|
||||
}
|
||||
addState<State.Canceled> {
|
||||
on<Event.Restart>(State.RequestingVerification)
|
||||
}
|
||||
addState<State.SasVerificationStarted> {
|
||||
on<Event.DidReceiveChallenge> { event, _ -> State.Verifying.ChallengeReceived(event.emojis) }
|
||||
}
|
||||
addState<State.Verifying.ChallengeReceived> {
|
||||
on<Event.AcceptChallenge> { _, prevState -> State.Verifying.Replying(prevState.emojis, true) }
|
||||
on<Event.DeclineChallenge> { _, prevState -> State.Verifying.Replying(prevState.emojis, false) }
|
||||
}
|
||||
addState<State.Verifying.Replying> {
|
||||
onEnter { state ->
|
||||
if (state.accept) {
|
||||
sessionVerificationService.approveVerification()
|
||||
} else {
|
||||
sessionVerificationService.declineVerification()
|
||||
init {
|
||||
spec {
|
||||
inState<State.Initial> {
|
||||
on { _: Event.RequestVerification, state: MachineState<State.Initial> ->
|
||||
state.override { State.RequestingVerification }
|
||||
}
|
||||
on { _: Event.StartSasVerification, state: MachineState<State.Initial> ->
|
||||
state.override { State.StartingSasVerification }
|
||||
}
|
||||
}
|
||||
on<Event.DidAcceptChallenge>(State.Completed)
|
||||
}
|
||||
addState<State.Canceling> {
|
||||
onEnter { sessionVerificationService.cancelVerification() }
|
||||
}
|
||||
on<Event.DidStartSasVerification>(State.SasVerificationStarted)
|
||||
on<Event.Cancel>(State.Canceling)
|
||||
on<Event.DidCancel>(State.Canceled)
|
||||
on<Event.DidFail>(State.Canceled)
|
||||
}
|
||||
|
||||
init {
|
||||
// Observe the verification service state, translate it to state machine input events
|
||||
sessionVerificationService.verificationFlowState.onEach { verificationAttemptState ->
|
||||
when (verificationAttemptState) {
|
||||
VerificationFlowState.Initial -> stateMachine.restart()
|
||||
VerificationFlowState.AcceptedVerificationRequest -> {
|
||||
stateMachine.process(Event.DidAcceptVerificationRequest)
|
||||
inState<State.RequestingVerification> {
|
||||
onEnterEffect {
|
||||
sessionVerificationService.requestVerification()
|
||||
}
|
||||
VerificationFlowState.StartedSasVerification -> {
|
||||
stateMachine.process(Event.DidStartSasVerification)
|
||||
on { _: Event.DidAcceptVerificationRequest, state: MachineState<State.RequestingVerification> ->
|
||||
state.override { State.VerificationRequestAccepted }
|
||||
}
|
||||
is VerificationFlowState.ReceivedVerificationData -> {
|
||||
// For some reason we receive this state twice, we need to discard the 2nd one
|
||||
if (stateMachine.currentState == State.SasVerificationStarted) {
|
||||
stateMachine.process(Event.DidReceiveChallenge(verificationAttemptState.emoji))
|
||||
on { _: Event.DidFail, state: MachineState<State.RequestingVerification> ->
|
||||
state.override { State.Initial }
|
||||
}
|
||||
}
|
||||
inState<State.StartingSasVerification> {
|
||||
onEnterEffect {
|
||||
sessionVerificationService.startVerification()
|
||||
}
|
||||
}
|
||||
inState<State.VerificationRequestAccepted> {
|
||||
on { _: Event.StartSasVerification, state: MachineState<State.VerificationRequestAccepted> ->
|
||||
state.override { State.StartingSasVerification }
|
||||
}
|
||||
}
|
||||
inState<State.Canceled> {
|
||||
on { _: Event.Restart, state: MachineState<State.Canceled> ->
|
||||
state.override { State.RequestingVerification }
|
||||
}
|
||||
}
|
||||
inState<State.SasVerificationStarted> {
|
||||
on { event: Event.DidReceiveChallenge, state: MachineState<State.SasVerificationStarted> ->
|
||||
state.override { State.Verifying.ChallengeReceived(event.emojis) }
|
||||
}
|
||||
}
|
||||
inState<State.Verifying.ChallengeReceived> {
|
||||
on { _: Event.AcceptChallenge, state: MachineState<State.Verifying.ChallengeReceived> ->
|
||||
state.override { State.Verifying.Replying(state.snapshot.emojis, accept = true) }
|
||||
}
|
||||
on { _: Event.DeclineChallenge, state: MachineState<State.Verifying.ChallengeReceived> ->
|
||||
state.override { State.Verifying.Replying(state.snapshot.emojis, accept = false) }
|
||||
}
|
||||
}
|
||||
inState<State.Verifying.Replying> {
|
||||
onEnterEffect { state ->
|
||||
if (state.accept) {
|
||||
sessionVerificationService.approveVerification()
|
||||
} else {
|
||||
sessionVerificationService.declineVerification()
|
||||
}
|
||||
}
|
||||
VerificationFlowState.Finished -> {
|
||||
stateMachine.process(Event.DidAcceptChallenge)
|
||||
}
|
||||
VerificationFlowState.Canceled -> {
|
||||
stateMachine.process(Event.DidCancel)
|
||||
}
|
||||
VerificationFlowState.Failed -> {
|
||||
stateMachine.process(Event.DidFail)
|
||||
on { _: Event.DidAcceptChallenge, state: MachineState<State.Verifying.Replying> ->
|
||||
state.override { State.Completed }
|
||||
}
|
||||
}
|
||||
}.launchIn(coroutineScope)
|
||||
inState<State.Canceling> {
|
||||
onEnterEffect {
|
||||
sessionVerificationService.cancelVerification()
|
||||
}
|
||||
}
|
||||
inState {
|
||||
on { _: Event.DidStartSasVerification, state: MachineState<State> ->
|
||||
state.override { State.SasVerificationStarted }
|
||||
}
|
||||
on { _: Event.Cancel, state: MachineState<State> ->
|
||||
if (state.snapshot in sequenceOf(
|
||||
State.Initial,
|
||||
State.Completed,
|
||||
State.Canceled
|
||||
)) {
|
||||
state.noChange()
|
||||
} else {
|
||||
state.override { State.Canceling }
|
||||
}
|
||||
}
|
||||
on { _: Event.DidCancel, state: MachineState<State> ->
|
||||
state.override { State.Canceled }
|
||||
}
|
||||
on { _: Event.DidFail, state: MachineState<State> ->
|
||||
state.override { State.Canceled }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val state: StateFlow<State> = stateMachine.stateFlow
|
||||
|
||||
fun process(event: Event) = stateMachine.process(event)
|
||||
|
||||
sealed interface State {
|
||||
/** The initial state, before verification started. */
|
||||
object Initial : State
|
||||
|
|
@ -134,10 +146,13 @@ class VerifySelfSessionStateMachine(
|
|||
/** Replying to a verification challenge. */
|
||||
data class Replying(override val emojis: List<VerificationEmoji>, val accept: Boolean) : Verifying(emojis)
|
||||
}
|
||||
|
||||
/** The verification is being canceled. */
|
||||
object Canceling : State
|
||||
|
||||
/** The verification has been canceled, remotely or locally. */
|
||||
object Canceled : State
|
||||
|
||||
/** Verification successful. */
|
||||
object Completed : State
|
||||
}
|
||||
|
|
@ -145,26 +160,37 @@ class VerifySelfSessionStateMachine(
|
|||
sealed interface Event {
|
||||
/** Request verification. */
|
||||
object RequestVerification : Event
|
||||
|
||||
/** The current verification request has been accepted. */
|
||||
object DidAcceptVerificationRequest : Event
|
||||
|
||||
/** Start a SaS verification flow. */
|
||||
object StartSasVerification : Event
|
||||
|
||||
/** Started a SaS verification flow. */
|
||||
object DidStartSasVerification : Event
|
||||
|
||||
/** Has received emojis. */
|
||||
data class DidReceiveChallenge(val emojis: List<VerificationEmoji>) : Event
|
||||
|
||||
/** Emojis match. */
|
||||
object AcceptChallenge : Event
|
||||
|
||||
/** Emojis do not match. */
|
||||
object DeclineChallenge : Event
|
||||
|
||||
/** Remote accepted challenge. */
|
||||
object DidAcceptChallenge : Event
|
||||
|
||||
/** Request cancellation. */
|
||||
object Cancel : Event
|
||||
|
||||
/** Verification cancelled. */
|
||||
object DidCancel : Event
|
||||
|
||||
/** Request failed. */
|
||||
object DidFail : Event
|
||||
|
||||
/** Restart the verification flow. */
|
||||
object Restart : Event
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,14 +18,13 @@ package io.element.android.features.verifysession.impl
|
|||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.Event
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep as VerificationStep
|
||||
import io.element.android.features.verifysession.impl.VerifySelfSessionState.VerificationStep
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationEmoji
|
||||
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -36,8 +35,7 @@ class VerifySelfSessionPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - Initial state is received`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -48,30 +46,18 @@ class VerifySelfSessionPresenterTests {
|
|||
@Test
|
||||
fun `present - Handles requestVerification`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
// Await for other device response:
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
// Await for the state to be Ready
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Ready)
|
||||
// Await for other device response (again):
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
// Finally, ChallengeReceived:
|
||||
val verifyingState = awaitItem()
|
||||
assertThat(verifyingState.verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
requestVerificationAndAwaitVerifyingState(service)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Handles startSasVerification`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -82,6 +68,7 @@ class VerifySelfSessionPresenterTests {
|
|||
// Await for other device response:
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
// ChallengeReceived:
|
||||
service.triggerReceiveVerificationData()
|
||||
val verifyingState = awaitItem()
|
||||
assertThat(verifyingState.verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
}
|
||||
|
|
@ -89,8 +76,7 @@ class VerifySelfSessionPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - Cancelation on initial state does nothing`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -105,65 +91,43 @@ class VerifySelfSessionPresenterTests {
|
|||
@Test
|
||||
fun `present - A fail in the flow cancels it`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
|
||||
val verifyingState = awaitChallengeReceivedState()
|
||||
assertThat(verifyingState.verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
service.shouldFail = true
|
||||
eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
||||
|
||||
val remainingEvents = cancelAndConsumeRemainingEvents().mapNotNull { (it as? Event.Item<VerifySelfSessionState>)?.value }
|
||||
assertThat(remainingEvents.last().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
state.eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
||||
// Cancelling
|
||||
assertThat(awaitItem().verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
// Cancelled
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Canceling the flow once it's verifying cancels it`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
|
||||
val verifyingState = awaitChallengeReceivedState()
|
||||
assertThat(verifyingState.verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
|
||||
eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
|
||||
val remainingEvents = cancelAndConsumeRemainingEvents().mapNotNull { (it as? Event.Item<VerifySelfSessionState>)?.value }
|
||||
assertThat(remainingEvents.last().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
state.eventSink(VerifySelfSessionViewEvents.CancelAndClose)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - When verifying, if we receive another challenge we ignore it`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
|
||||
val verifyingState = awaitChallengeReceivedState()
|
||||
assertThat(verifyingState.verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
|
||||
requestVerificationAndAwaitVerifyingState(service)
|
||||
service.givenVerificationFlowState(VerificationFlowState.ReceivedVerificationData(emptyList()))
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
|
|
@ -171,21 +135,14 @@ class VerifySelfSessionPresenterTests {
|
|||
@Test
|
||||
fun `present - Restart after cancelation returns to requesting verification`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
assertThat(awaitChallengeReceivedState().verificationFlowStep).isEqualTo(VerificationStep.Verifying(emptyList(), Async.Uninitialized))
|
||||
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
service.givenVerificationFlowState(VerificationFlowState.Canceled)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
|
||||
eventSink(VerifySelfSessionViewEvents.Restart)
|
||||
state.eventSink(VerifySelfSessionViewEvents.Restart)
|
||||
// Went back to requesting verification
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
|
@ -200,18 +157,12 @@ class VerifySelfSessionPresenterTests {
|
|||
val service = FakeSessionVerificationService().apply {
|
||||
givenEmojiList(emojis)
|
||||
}
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
assertThat(awaitChallengeReceivedState().verificationFlowStep).isEqualTo(VerificationStep.Verifying(emojis, Async.Uninitialized))
|
||||
|
||||
eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
state.eventSink(VerifySelfSessionViewEvents.ConfirmVerification)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Verifying(emojis, Async.Loading()))
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Completed)
|
||||
}
|
||||
|
|
@ -220,28 +171,41 @@ class VerifySelfSessionPresenterTests {
|
|||
@Test
|
||||
fun `present - When verification is declined, the flow is canceled`() = runTest {
|
||||
val service = FakeSessionVerificationService()
|
||||
val presenter = VerifySelfSessionPresenter(service)
|
||||
val presenter = createPresenter(service)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
val eventSink = initialState.eventSink
|
||||
|
||||
eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
|
||||
assertThat(awaitChallengeReceivedState().verificationFlowStep).isEqualTo(VerificationStep.Verifying(emptyList(), Async.Uninitialized))
|
||||
eventSink(VerifySelfSessionViewEvents.DeclineVerification)
|
||||
|
||||
val state = requestVerificationAndAwaitVerifyingState(service)
|
||||
state.eventSink(VerifySelfSessionViewEvents.DeclineVerification)
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Verifying(emptyList(), Async.Loading()))
|
||||
assertThat(awaitItem().verificationFlowStep).isEqualTo(VerificationStep.Canceled)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun ReceiveTurbine<VerifySelfSessionState>.awaitChallengeReceivedState(): VerifySelfSessionState {
|
||||
// Skip 'waiting for response', 'ready' and 'starting verification' state
|
||||
skipItems(3)
|
||||
// Received challenge
|
||||
return awaitItem()
|
||||
private suspend fun ReceiveTurbine<VerifySelfSessionState>.requestVerificationAndAwaitVerifyingState(
|
||||
fakeService: FakeSessionVerificationService
|
||||
): VerifySelfSessionState {
|
||||
var state = awaitItem()
|
||||
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Initial)
|
||||
state.eventSink(VerifySelfSessionViewEvents.RequestVerification)
|
||||
// Await for other device response:
|
||||
state = awaitItem()
|
||||
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
// Await for the state to be Ready
|
||||
state = awaitItem()
|
||||
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.Ready)
|
||||
state.eventSink(VerifySelfSessionViewEvents.StartSasVerification)
|
||||
// Await for other device response (again):
|
||||
state = awaitItem()
|
||||
assertThat(state.verificationFlowStep).isEqualTo(VerificationStep.AwaitingOtherDeviceResponse)
|
||||
fakeService.triggerReceiveVerificationData()
|
||||
// Finally, ChallengeReceived:
|
||||
state = awaitItem()
|
||||
assertThat(state.verificationFlowStep).isInstanceOf(VerificationStep.Verifying::class.java)
|
||||
return state
|
||||
}
|
||||
|
||||
private fun createPresenter(service: FakeSessionVerificationService = FakeSessionVerificationService()): VerifySelfSessionPresenter {
|
||||
return VerifySelfSessionPresenter(service, VerifySelfSessionStateMachine(service))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue