From 7fd0ad09dc7069ab6e4280229d752e10dff6164b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Mon, 12 Aug 2024 12:22:36 +0200 Subject: [PATCH] Improve APIs, add tests --- .../impl/reset/ResetIdentityFlowManager.kt | 5 - .../impl/reset/ResetIdentityFlowNode.kt | 12 +- ...Event.kt => ResetIdentityPasswordEvent.kt} | 6 +- ...rdNode.kt => ResetIdentityPasswordNode.kt} | 12 +- ...r.kt => ResetIdentityPasswordPresenter.kt} | 22 ++-- ...State.kt => ResetIdentityPasswordState.kt} | 4 +- ...rdView.kt => ResetIdentityPasswordView.kt} | 14 +-- ...RootEvent.kt => ResetIdentityRootEvent.kt} | 6 +- ...eyRootNode.kt => ResetIdentityRootNode.kt} | 6 +- ...enter.kt => ResetIdentityRootPresenter.kt} | 12 +- ...RootState.kt => ResetIdentityRootState.kt} | 4 +- ...r.kt => ResetIdentityRootStateProvider.kt} | 8 +- ...eyRootView.kt => ResetIdentityRootView.kt} | 14 +-- .../reset/ResetIdentityFlowManagerTests.kt | 119 ++++++++++++++++++ .../ResetIdentityPasswordPresenterTest.kt | 96 ++++++++++++++ .../password/ResetIdentityPasswordViewTest.kt | 97 ++++++++++++++ .../root/ResetIdentityRootPresenterTests.kt | 65 ++++++++++ .../reset/root/ResetIdentityRootViewTests.kt | 107 ++++++++++++++++ .../api/encryption/EncryptionService.kt | 4 +- .../impl/encryption/RustEncryptionService.kt | 8 +- .../encryption/RustIdentityResetHandle.kt | 10 +- .../encryption/FakeIdentityResetHandle.kt | 7 +- 22 files changed, 562 insertions(+), 76 deletions(-) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/{ResetKeyPasswordEvent.kt => ResetIdentityPasswordEvent.kt} (79%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/{ResetKeyPasswordNode.kt => ResetIdentityPasswordNode.kt} (81%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/{ResetKeyPasswordPresenter.kt => ResetIdentityPasswordPresenter.kt} (68%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/{ResetKeyPasswordState.kt => ResetIdentityPasswordState.kt} (89%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/{ResetKeyPasswordView.kt => ResetIdentityPasswordView.kt} (92%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/{ResetKeyRootEvent.kt => ResetIdentityRootEvent.kt} (82%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/{ResetKeyRootNode.kt => ResetIdentityRootNode.kt} (91%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/{ResetKeyRootPresenter.kt => ResetIdentityRootPresenter.kt} (78%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/{ResetKeyRootState.kt => ResetIdentityRootState.kt} (89%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/{ResetKeyRootStateProvider.kt => ResetIdentityRootStateProvider.kt} (81%) rename features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/{ResetKeyRootView.kt => ResetIdentityRootView.kt} (92%) create mode 100644 features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTests.kt create mode 100644 features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt create mode 100644 features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt create mode 100644 features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTests.kt create mode 100644 features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTests.kt diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt index fa4f693cd3..3425ca90be 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManager.kt @@ -19,7 +19,6 @@ package io.element.android.features.securebackup.impl.reset import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus @@ -46,10 +45,6 @@ class ResetIdentityFlowManager @Inject constructor( } } - fun currentSessionId(): SessionId { - return matrixClient.sessionId - } - fun getResetHandle(): StateFlow> { return if (resetHandleFlow.value.isLoading() || resetHandleFlow.value.isSuccess()) { resetHandleFlow diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt index e144a52432..0f0278cb66 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowNode.kt @@ -34,8 +34,8 @@ import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode -import io.element.android.features.securebackup.impl.reset.password.ResetKeyPasswordNode -import io.element.android.features.securebackup.impl.reset.root.ResetKeyRootNode +import io.element.android.features.securebackup.impl.reset.password.ResetIdentityPasswordNode +import io.element.android.features.securebackup.impl.reset.root.ResetIdentityRootNode import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.BackstackView @@ -108,18 +108,18 @@ class ResetIdentityFlowNode @AssistedInject constructor( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.Root -> { - val callback = object : ResetKeyRootNode.Callback { + val callback = object : ResetIdentityRootNode.Callback { override fun onContinue() { coroutineScope.startReset() } } - createNode(buildContext, listOf(callback)) + createNode(buildContext, listOf(callback)) } is NavTarget.ResetPassword -> { val handle = resetIdentityFlowManager.currentHandleFlow.value.dataOrNull() as? IdentityPasswordResetHandle ?: error("No password handle found") - createNode( + createNode( buildContext, - listOf(ResetKeyPasswordNode.Inputs(resetIdentityFlowManager.currentSessionId(), handle)) + listOf(ResetIdentityPasswordNode.Inputs(handle)) ) } is NavTarget.ResetOidc -> { diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordEvent.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt similarity index 79% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordEvent.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt index 5fce7cdf85..b76dff920f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordEvent.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordEvent.kt @@ -16,7 +16,7 @@ package io.element.android.features.securebackup.impl.reset.password -sealed interface ResetKeyPasswordEvent { - data class Reset(val password: String) : ResetKeyPasswordEvent - data object DismissError : ResetKeyPasswordEvent +sealed interface ResetIdentityPasswordEvent { + data class Reset(val password: String) : ResetIdentityPasswordEvent + data object DismissError : ResetIdentityPasswordEvent } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt similarity index 81% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordNode.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt index c430bbec28..7397361b03 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordNode.kt @@ -21,33 +21,33 @@ import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin -import com.bumble.appyx.core.plugin.plugins import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.inputs +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle @ContributesNode(SessionScope::class) -class ResetKeyPasswordNode @AssistedInject constructor( +class ResetIdentityPasswordNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, + private val coroutineDispatchers: CoroutineDispatchers, ) : Node(buildContext, plugins = plugins) { - data class Inputs(val userId: UserId, val handle: IdentityPasswordResetHandle) : NodeInputs + data class Inputs(val handle: IdentityPasswordResetHandle) : NodeInputs private val presenter by lazy { val inputs = inputs() - ResetKeyPasswordPresenter(inputs.userId, inputs.handle) + ResetIdentityPasswordPresenter(inputs.handle, dispatchers = coroutineDispatchers) } @Composable override fun View(modifier: Modifier) { val state = presenter.present() - ResetKeyPasswordView( + ResetIdentityPasswordView( state = state, onBack = ::navigateUp ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt similarity index 68% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordPresenter.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt index 19fa002108..baa8ab2844 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenter.kt @@ -24,37 +24,37 @@ import androidx.compose.runtime.rememberCoroutineScope import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState -import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch -class ResetKeyPasswordPresenter( - private val userId: UserId, +class ResetIdentityPasswordPresenter( private val identityPasswordResetHandle: IdentityPasswordResetHandle, -) : Presenter { + private val dispatchers: CoroutineDispatchers, +) : Presenter { @Composable - override fun present(): ResetKeyPasswordState { + override fun present(): ResetIdentityPasswordState { val coroutineScope = rememberCoroutineScope() val resetAction = remember { mutableStateOf>(AsyncAction.Uninitialized) } - fun handleEvent(event: ResetKeyPasswordEvent) { + fun handleEvent(event: ResetIdentityPasswordEvent) { when (event) { - is ResetKeyPasswordEvent.Reset -> coroutineScope.reset(userId, event.password, resetAction) - ResetKeyPasswordEvent.DismissError -> resetAction.value = AsyncAction.Uninitialized + is ResetIdentityPasswordEvent.Reset -> coroutineScope.reset(event.password, resetAction) + ResetIdentityPasswordEvent.DismissError -> resetAction.value = AsyncAction.Uninitialized } } - return ResetKeyPasswordState( + return ResetIdentityPasswordState( resetAction = resetAction.value, eventSink = ::handleEvent ) } - private fun CoroutineScope.reset(userId: UserId, password: String, action: MutableState>) = launch { + private fun CoroutineScope.reset(password: String, action: MutableState>) = launch(dispatchers.io) { suspend { - identityPasswordResetHandle.resetPassword(userId, password).getOrThrow() + identityPasswordResetHandle.resetPassword(password).getOrThrow() }.runCatchingUpdatingState(action) } } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt similarity index 89% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordState.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt index 3de58ec032..47662b623e 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordState.kt @@ -18,7 +18,7 @@ package io.element.android.features.securebackup.impl.reset.password import io.element.android.libraries.architecture.AsyncAction -data class ResetKeyPasswordState( +data class ResetIdentityPasswordState( val resetAction: AsyncAction, - val eventSink: (ResetKeyPasswordEvent) -> Unit, + val eventSink: (ResetIdentityPasswordEvent) -> Unit, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt similarity index 92% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordView.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt index d9339064f2..d64e451a9f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetKeyPasswordView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordView.kt @@ -46,8 +46,8 @@ import io.element.android.libraries.designsystem.theme.components.onTabOrEnterKe import io.element.android.libraries.ui.strings.CommonStrings @Composable -fun ResetKeyPasswordView( - state: ResetKeyPasswordState, +fun ResetIdentityPasswordView( + state: ResetIdentityPasswordState, onBack: () -> Unit, modifier: Modifier = Modifier, ) { @@ -63,7 +63,7 @@ fun ResetKeyPasswordView( Button( modifier = Modifier.fillMaxWidth(), text = stringResource(CommonStrings.action_reset_identity), - onClick = { state.eventSink(ResetKeyPasswordEvent.Reset(passwordState.value)) }, + onClick = { state.eventSink(ResetIdentityPasswordEvent.Reset(passwordState.value)) }, destructive = true, ) } @@ -74,7 +74,7 @@ fun ResetKeyPasswordView( } else if (state.resetAction.isFailure()) { ErrorDialog( content = stringResource(CommonStrings.error_unknown), - onDismiss = { state.eventSink(ResetKeyPasswordEvent.DismissError) } + onDismiss = { state.eventSink(ResetIdentityPasswordEvent.DismissError) } ) } } @@ -107,10 +107,10 @@ private fun Content(textFieldState: MutableState) { @PreviewsDayNight @Composable -internal fun ResetKeyPasswordViewPreview() { +internal fun ResetIdentityPasswordViewPreview() { ElementPreview { - ResetKeyPasswordView( - state = ResetKeyPasswordState( + ResetIdentityPasswordView( + state = ResetIdentityPasswordState( resetAction = AsyncAction.Uninitialized, eventSink = {} ), diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootEvent.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt similarity index 82% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootEvent.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt index 268228ac8b..a1ec4cbe82 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootEvent.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootEvent.kt @@ -16,7 +16,7 @@ package io.element.android.features.securebackup.impl.reset.root -sealed interface ResetKeyRootEvent { - data object Continue : ResetKeyRootEvent - data object DismissDialog : ResetKeyRootEvent +sealed interface ResetIdentityRootEvent { + data object Continue : ResetIdentityRootEvent + data object DismissDialog : ResetIdentityRootEvent } diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootNode.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt similarity index 91% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootNode.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt index b7171aaa7b..9fc5061ae2 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootNode.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootNode.kt @@ -27,7 +27,7 @@ import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.SessionScope @ContributesNode(SessionScope::class) -class ResetKeyRootNode @AssistedInject constructor( +class ResetIdentityRootNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, ) : Node(buildContext, plugins = plugins) { @@ -35,13 +35,13 @@ class ResetKeyRootNode @AssistedInject constructor( fun onContinue() } - private val presenter = ResetKeyRootPresenter() + private val presenter = ResetIdentityRootPresenter() private val callback: Callback = plugins.filterIsInstance().first() @Composable override fun View(modifier: Modifier) { val state = presenter.present() - ResetKeyRootView( + ResetIdentityRootView( state = state, onContinue = callback::onContinue, onBack = ::navigateUp, diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootPresenter.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt similarity index 78% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootPresenter.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt index d2e3e5dc70..11c96e9ad8 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootPresenter.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenter.kt @@ -23,19 +23,19 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Presenter -class ResetKeyRootPresenter : Presenter { +class ResetIdentityRootPresenter : Presenter { @Composable - override fun present(): ResetKeyRootState { + override fun present(): ResetIdentityRootState { var displayConfirmDialog by remember { mutableStateOf(false) } - fun handleEvent(event: ResetKeyRootEvent) { + fun handleEvent(event: ResetIdentityRootEvent) { displayConfirmDialog = when (event) { - ResetKeyRootEvent.Continue -> true - ResetKeyRootEvent.DismissDialog -> false + ResetIdentityRootEvent.Continue -> true + ResetIdentityRootEvent.DismissDialog -> false } } - return ResetKeyRootState( + return ResetIdentityRootState( displayConfirmationDialog = displayConfirmDialog, eventSink = ::handleEvent ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootState.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt similarity index 89% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootState.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt index faaee64040..de1054c97f 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootState.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootState.kt @@ -16,7 +16,7 @@ package io.element.android.features.securebackup.impl.reset.root -data class ResetKeyRootState( +data class ResetIdentityRootState( val displayConfirmationDialog: Boolean, - val eventSink: (ResetKeyRootEvent) -> Unit, + val eventSink: (ResetIdentityRootEvent) -> Unit, ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootStateProvider.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt similarity index 81% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootStateProvider.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt index 15299fa5ba..8d780343fe 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootStateProvider.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootStateProvider.kt @@ -18,14 +18,14 @@ package io.element.android.features.securebackup.impl.reset.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider -class ResetKeyRootStateProvider : PreviewParameterProvider { - override val values: Sequence +class ResetIdentityRootStateProvider : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - ResetKeyRootState( + ResetIdentityRootState( displayConfirmationDialog = false, eventSink = {} ), - ResetKeyRootState( + ResetIdentityRootState( displayConfirmationDialog = true, eventSink = {} ) diff --git a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootView.kt b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt similarity index 92% rename from features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootView.kt rename to features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt index cbeaa1a14e..8020aee5c6 100644 --- a/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetKeyRootView.kt +++ b/features/securebackup/impl/src/main/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootView.kt @@ -43,8 +43,8 @@ import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.persistentListOf @Composable -fun ResetKeyRootView( - state: ResetKeyRootState, +fun ResetIdentityRootView( + state: ResetIdentityRootState, onContinue: () -> Unit, onBack: () -> Unit, ) { @@ -58,7 +58,7 @@ fun ResetKeyRootView( Button( modifier = Modifier.fillMaxWidth(), text = stringResource(id = CommonStrings.action_continue), - onClick = { state.eventSink(ResetKeyRootEvent.Continue) }, + onClick = { state.eventSink(ResetIdentityRootEvent.Continue) }, destructive = true, ) }, @@ -71,11 +71,11 @@ fun ResetKeyRootView( content = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_subtitle), submitText = stringResource(CommonStrings.screen_reset_encryption_confirmation_alert_action), onSubmitClick = { - state.eventSink(ResetKeyRootEvent.DismissDialog) + state.eventSink(ResetIdentityRootEvent.DismissDialog) onContinue() }, destructiveSubmit = true, - onDismiss = { state.eventSink(ResetKeyRootEvent.DismissDialog) } + onDismiss = { state.eventSink(ResetIdentityRootEvent.DismissDialog) } ) } } @@ -138,9 +138,9 @@ private fun Content() { @PreviewsDayNight @Composable -internal fun ResetKeyRootViewPreview(@PreviewParameter(ResetKeyRootStateProvider::class) state: ResetKeyRootState) { +internal fun ResetIdentityRootViewPreview(@PreviewParameter(ResetIdentityRootStateProvider::class) state: ResetIdentityRootState) { ElementPreview { - ResetKeyRootView( + ResetIdentityRootView( state = state, onContinue = {}, onBack = {}, diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTests.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTests.kt new file mode 100644 index 0000000000..6669d0cc90 --- /dev/null +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/ResetIdentityFlowManagerTests.kt @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.reset + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService +import io.element.android.libraries.matrix.test.encryption.FakeIdentityPasswordResetHandle +import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.tests.testutils.lambda.lambdaRecorder +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ResetIdentityFlowManagerTests { + @Test + fun `getResetHandle - emits a reset handle`() = runTest { + val startResetLambda = lambdaRecorder> { Result.success(FakeIdentityPasswordResetHandle()) } + val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda) + val flowManager = createFlowManager(encryptionService = encryptionService) + + flowManager.getResetHandle().test { + assertThat(awaitItem().isLoading()).isTrue() + assertThat(awaitItem().isSuccess()).isTrue() + startResetLambda.assertions().isCalledOnce() + } + } + + @Test + fun `getResetHandle - om successful handle retrieval returns that same handle`() = runTest { + val startResetLambda = lambdaRecorder> { Result.success(FakeIdentityPasswordResetHandle()) } + val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda) + val flowManager = createFlowManager(encryptionService = encryptionService) + + var result: AsyncData.Success? = null + flowManager.getResetHandle().test { + assertThat(awaitItem().isLoading()).isTrue() + result = awaitItem() as? AsyncData.Success + assertThat(result).isNotNull() + } + + flowManager.getResetHandle().test { + assertThat(awaitItem()).isSameInstanceAs(result) + } + } + + @Test + fun `getResetHandle - will fail if it receives a null reset handle`() = runTest { + val startResetLambda = lambdaRecorder> { Result.success(null) } + val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda) + val flowManager = createFlowManager(encryptionService = encryptionService) + + flowManager.getResetHandle().test { + assertThat(awaitItem().isLoading()).isTrue() + assertThat(awaitItem().isFailure()).isTrue() + startResetLambda.assertions().isCalledOnce() + } + } + + @Test + fun `getResetHandle - fails gracefully when receiving an exception from the encryption service`() = runTest { + val startResetLambda = lambdaRecorder> { Result.failure(IllegalStateException("Failure")) } + val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda) + val flowManager = createFlowManager(encryptionService = encryptionService) + + flowManager.getResetHandle().test { + assertThat(awaitItem().isLoading()).isTrue() + assertThat(awaitItem().isFailure()).isTrue() + startResetLambda.assertions().isCalledOnce() + } + } + + @Test + fun `cancel - resets the state and calls cancel on the reset handle`() = runTest { + val cancelLambda = lambdaRecorder { } + val resetHandle = FakeIdentityPasswordResetHandle(cancelLambda = cancelLambda) + val startResetLambda = lambdaRecorder> { Result.success(resetHandle) } + val encryptionService = FakeEncryptionService(startIdentityResetLambda = startResetLambda) + val flowManager = createFlowManager(encryptionService = encryptionService) + + flowManager.getResetHandle().test { + assertThat(awaitItem().isLoading()).isTrue() + assertThat(awaitItem().isSuccess()).isTrue() + + flowManager.cancel() + cancelLambda.assertions().isCalledOnce() + assertThat(awaitItem().isUninitialized()).isTrue() + } + } + + private fun TestScope.createFlowManager( + encryptionService: FakeEncryptionService = FakeEncryptionService(), + client: FakeMatrixClient = FakeMatrixClient(encryptionService = encryptionService), + sessionCoroutineScope: CoroutineScope = this, + sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(), + ) = ResetIdentityFlowManager( + matrixClient = client, + sessionCoroutineScope = sessionCoroutineScope, + sessionVerificationService = sessionVerificationService, + ) +} diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt new file mode 100644 index 0000000000..059983df3d --- /dev/null +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordPresenterTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.reset.password + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.test.encryption.FakeIdentityPasswordResetHandle +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ResetIdentityPasswordPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.resetAction.isUninitialized()).isTrue() + } + } + + @Test + fun `present - Reset event succeeds`() = runTest { + val resetLambda = lambdaRecorder> { _ -> Result.success(Unit) } + val resetHandle = FakeIdentityPasswordResetHandle(resetPasswordLambda = resetLambda) + val presenter = createPresenter(identityResetHandle = resetHandle) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(ResetIdentityPasswordEvent.Reset("password")) + assertThat(awaitItem().resetAction.isLoading()).isTrue() + assertThat(awaitItem().resetAction.isSuccess()).isTrue() + } + } + + @Test + fun `present - Reset event can fail gracefully`() = runTest { + val resetLambda = lambdaRecorder> { _ -> Result.failure(IllegalStateException("Failed")) } + val resetHandle = FakeIdentityPasswordResetHandle(resetPasswordLambda = resetLambda) + val presenter = createPresenter(identityResetHandle = resetHandle) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(ResetIdentityPasswordEvent.Reset("password")) + assertThat(awaitItem().resetAction.isLoading()).isTrue() + assertThat(awaitItem().resetAction.isFailure()).isTrue() + } + } + + @Test + fun `present - DismissError event resets the state`() = runTest { + val resetLambda = lambdaRecorder> { _ -> Result.failure(IllegalStateException("Failed")) } + val resetHandle = FakeIdentityPasswordResetHandle(resetPasswordLambda = resetLambda) + val presenter = createPresenter(identityResetHandle = resetHandle) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(ResetIdentityPasswordEvent.Reset("password")) + assertThat(awaitItem().resetAction.isLoading()).isTrue() + assertThat(awaitItem().resetAction.isFailure()).isTrue() + + initialState.eventSink(ResetIdentityPasswordEvent.DismissError) + assertThat(awaitItem().resetAction.isUninitialized()).isTrue() + } + } + + private fun TestScope.createPresenter( + identityResetHandle: FakeIdentityPasswordResetHandle = FakeIdentityPasswordResetHandle(), + ) = ResetIdentityPasswordPresenter( + identityPasswordResetHandle = identityResetHandle, + dispatchers = testCoroutineDispatchers(), + ) +} diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt new file mode 100644 index 0000000000..17029452db --- /dev/null +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/password/ResetIdentityPasswordViewTest.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.reset.password + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performTextInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ResetIdentityPasswordViewTest { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `pressing the back HW button invokes the expected callback`() { + ensureCalledOnce { + rule.setResetPasswordView( + ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = {}), + onBack = it, + ) + rule.pressBackKey() + } + } + + @Test + fun `clicking on the back navigation button invokes the expected callback`() { + ensureCalledOnce { + rule.setResetPasswordView( + ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = {}), + onBack = it, + ) + rule.pressBack() + } + } + + @Test + fun `clicking 'Reset identity' confirms the reset`() { + val eventsRecorder = EventsRecorder() + rule.setResetPasswordView( + ResetIdentityPasswordState(resetAction = AsyncAction.Uninitialized, eventSink = eventsRecorder), + ) + rule.onNodeWithText("Password").performTextInput("A password") + + rule.clickOn(CommonStrings.action_reset_identity) + + eventsRecorder.assertSingle(ResetIdentityPasswordEvent.Reset("A password")) + } + + @Test + fun `clicking OK dismisses the error dialog`() { + val eventsRecorder = EventsRecorder() + rule.setResetPasswordView( + ResetIdentityPasswordState(resetAction = AsyncAction.Failure(IllegalStateException("A failure")), eventSink = eventsRecorder), + ) + rule.clickOn(CommonStrings.action_ok) + + eventsRecorder.assertSingle(ResetIdentityPasswordEvent.DismissError) + } +} + +private fun AndroidComposeTestRule.setResetPasswordView( + state: ResetIdentityPasswordState, + onBack: () -> Unit = EnsureNeverCalled(), +) { + setContent { + ResetIdentityPasswordView(state = state, onBack = onBack) + } +} diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTests.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTests.kt new file mode 100644 index 0000000000..5b669a0dc7 --- /dev/null +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootPresenterTests.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.reset.root + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class ResetIdentityRootPresenterTests { + @Test + fun `present - initial state`() = runTest { + val presenter = ResetIdentityRootPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + assertThat(initialState.displayConfirmationDialog).isFalse() + } + } + + @Test + fun `present - Continue event displays the confirmation dialog`() = runTest { + val presenter = ResetIdentityRootPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(ResetIdentityRootEvent.Continue) + + assertThat(awaitItem().displayConfirmationDialog).isTrue() + } + } + + @Test + fun `present - DismissDialog event hides the confirmation dialog`() = runTest { + val presenter = ResetIdentityRootPresenter() + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(ResetIdentityRootEvent.Continue) + assertThat(awaitItem().displayConfirmationDialog).isTrue() + + initialState.eventSink(ResetIdentityRootEvent.DismissDialog) + assertThat(awaitItem().displayConfirmationDialog).isFalse() + } + } +} diff --git a/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTests.kt b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTests.kt new file mode 100644 index 0000000000..9367b3c9e0 --- /dev/null +++ b/features/securebackup/impl/src/test/kotlin/io/element/android/features/securebackup/impl/reset/root/ResetIdentityRootViewTests.kt @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.securebackup.impl.reset.root + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.ui.strings.CommonStrings +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.clickOn +import io.element.android.tests.testutils.ensureCalledOnce +import io.element.android.tests.testutils.pressBack +import io.element.android.tests.testutils.pressBackKey +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith +import org.robolectric.annotation.Config + +@RunWith(AndroidJUnit4::class) +class ResetIdentityRootViewTests { + @get:Rule + val rule = createAndroidComposeRule() + + @Test + fun `pressing the back HW button invokes the expected callback`() { + ensureCalledOnce { + rule.setResetRootView( + ResetIdentityRootState(displayConfirmationDialog = false, eventSink = {}), + onBack = it, + ) + rule.pressBackKey() + } + } + + @Test + fun `clicking on the back navigation button invokes the expected callback`() { + ensureCalledOnce { + rule.setResetRootView( + ResetIdentityRootState(displayConfirmationDialog = false, eventSink = {}), + onBack = it, + ) + rule.pressBack() + } + } + + @Test + @Config(qualifiers = "h720dp") + fun `clicking Continue displays the confirmation dialog`() { + val eventsRecorder = EventsRecorder() + rule.setResetRootView( + ResetIdentityRootState(displayConfirmationDialog = false, eventSink = eventsRecorder), + ) + + rule.clickOn(CommonStrings.action_continue) + + eventsRecorder.assertSingle(ResetIdentityRootEvent.Continue) + } + + @Test + fun `clicking 'Yes, reset now' confirms the reset`() { + ensureCalledOnce { + rule.setResetRootView( + ResetIdentityRootState(displayConfirmationDialog = true, eventSink = {}), + onContinue = it, + ) + rule.clickOn(CommonStrings.screen_reset_encryption_confirmation_alert_action) + } + } + + @Test + fun `clicking Cancel dismisses the dialog`() { + val eventsRecorder = EventsRecorder() + rule.setResetRootView( + ResetIdentityRootState(displayConfirmationDialog = true, eventSink = eventsRecorder), + ) + + rule.clickOn(CommonStrings.action_cancel) + eventsRecorder.assertSingle(ResetIdentityRootEvent.DismissDialog) + } +} + +private fun AndroidComposeTestRule.setResetRootView( + state: ResetIdentityRootState, + onBack: () -> Unit = EnsureNeverCalled(), + onContinue: () -> Unit = EnsureNeverCalled(), +) { + setContent { + ResetIdentityRootView(state = state, onContinue = onContinue, onBack = onBack) + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt index 420940aefe..5afb81648b 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/EncryptionService.kt @@ -16,7 +16,6 @@ package io.element.android.libraries.matrix.api.encryption -import io.element.android.libraries.matrix.api.core.UserId import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -90,10 +89,9 @@ interface IdentityPasswordResetHandle : IdentityResetHandle { * This method will block the coroutine it's running on and keep polling indefinitely until either the coroutine is cancelled, the [cancel] method is * called, or the identity is reset. * - * @param userId the user id of the user to reset the password for. * @param password the current password, which will be validated before the process takes place. */ - suspend fun resetPassword(userId: UserId, password: String): Result + suspend fun resetPassword(password: String): Result } /** diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt index 728fbede55..ae681b2771 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustEncryptionService.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.encryption import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.extensions.mapFailure +import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.encryption.BackupState import io.element.android.libraries.matrix.api.encryption.BackupUploadState import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress @@ -55,6 +56,7 @@ internal class RustEncryptionService( private val dispatchers: CoroutineDispatchers, ) : EncryptionService { private val service: Encryption = client.encryption() + private val sessionId = SessionId(client.session().userId) private val enableRecoveryProgressMapper = EnableRecoveryProgressMapper() private val backupUploadStateMapper = BackupUploadStateMapper() @@ -201,6 +203,10 @@ internal class RustEncryptionService( } override suspend fun startIdentityReset(): Result { - return runCatching { service.resetIdentity()?.let(RustIdentityResetHandleFactory::create)?.getOrNull() } + return runCatching { + service.resetIdentity()?.let { handle -> + RustIdentityResetHandleFactory.create(sessionId, handle) + }?.getOrNull() + } } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt index 0d51d7c4c2..c4c20eb7d6 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/encryption/RustIdentityResetHandle.kt @@ -25,21 +25,25 @@ import org.matrix.rustcomponents.sdk.AuthDataPasswordDetails import org.matrix.rustcomponents.sdk.CrossSigningResetAuthType object RustIdentityResetHandleFactory { - fun create(identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle): Result { + fun create( + userId: UserId, + identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle + ): Result { return runCatching { when (val authType = identityResetHandle.authType()) { is CrossSigningResetAuthType.Oidc -> RustOidcIdentityResetHandle(identityResetHandle, authType.info.approvalUrl) // User interactive authentication (user + password) - CrossSigningResetAuthType.Uiaa -> RustPasswordIdentityResetHandle(identityResetHandle) + CrossSigningResetAuthType.Uiaa -> RustPasswordIdentityResetHandle(userId, identityResetHandle) } } } } class RustPasswordIdentityResetHandle( + private val userId: UserId, private val identityResetHandle: org.matrix.rustcomponents.sdk.IdentityResetHandle, ) : IdentityPasswordResetHandle { - override suspend fun resetPassword(userId: UserId, password: String): Result { + override suspend fun resetPassword(password: String): Result { return runCatching { identityResetHandle.reset(AuthData.Password(AuthDataPasswordDetails(userId.value, password))) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt index 6b3eabd102..69087163d2 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/encryption/FakeIdentityResetHandle.kt @@ -16,7 +16,6 @@ package io.element.android.libraries.matrix.test.encryption -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle @@ -35,11 +34,11 @@ class FakeIdentityOidcResetHandle( } class FakeIdentityPasswordResetHandle( - var resetPasswordLambda: (UserId, String) -> Result = { _, _ -> error("Not implemented") }, + var resetPasswordLambda: (String) -> Result = { _ -> error("Not implemented") }, var cancelLambda: () -> Unit = { error("Not implemented") }, ) : IdentityPasswordResetHandle { - override suspend fun resetPassword(userId: UserId, password: String): Result { - return resetPasswordLambda(userId, password) + override suspend fun resetPassword(password: String): Result { + return resetPasswordLambda(password) } override suspend fun cancel() {