diff --git a/.github/workflows/triage-move-review-requests.yml b/.github/workflows/triage-move-review-requests.yml index 61f1f114dd..6aeba66ccc 100644 --- a/.github/workflows/triage-move-review-requests.yml +++ b/.github/workflows/triage-move-review-requests.yml @@ -60,8 +60,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!, $contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } @@ -129,8 +129,8 @@ jobs: headers: '{"GraphQL-Features": "projects_next_graphql"}' query: | mutation add_to_project($projectid:ID!, $contentid:ID!) { - addProjectNextItem(input:{projectId:$projectid contentId:$contentid}) { - projectNextItem { + addProjectV2ItemById(input: {projectId: $projectid contentId: $contentid}) { + item { id } } diff --git a/CHANGES.md b/CHANGES.md index cfea189c21..18bb2480c3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +Changes in Element v1.5.7 (2022-11-07) +====================================== + +Bugfixes 🐛 +---------- +- Fix regression when syncing with homeserver < 1.4. ([#7534](https://github.com/vector-im/element-android/issues/7534)) + Changes in Element v1.5.6 (2022-11-02) ====================================== diff --git a/changelog.d/7501.bugfix b/changelog.d/7501.bugfix new file mode 100644 index 0000000000..b86258d427 --- /dev/null +++ b/changelog.d/7501.bugfix @@ -0,0 +1 @@ +Fix duplicated mention pills in some cases diff --git a/changelog.d/7509.bugfix b/changelog.d/7509.bugfix new file mode 100644 index 0000000000..93ec812e0e --- /dev/null +++ b/changelog.d/7509.bugfix @@ -0,0 +1 @@ +When joining a room, the message composer is displayed once the room is loaded. diff --git a/dependencies.gradle b/dependencies.gradle index 47f1097446..751cba4d44 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -26,7 +26,7 @@ def jjwt = "0.11.5" // Temporary version to unblock #6929. Once 0.16.0 is released we should use it, and revert // the whole commit which set version 0.16.0-SNAPSHOT def vanniktechEmoji = "0.16.0-SNAPSHOT" -def sentry = "6.6.0" +def sentry = "6.7.0" def fragment = "1.5.4" // Testing def mockk = "1.12.3" // We need to use 1.12.3 to have mocking in androidTest until a new version is released: https://github.com/mockk/mockk/issues/819 diff --git a/fastlane/metadata/android/en-US/changelogs/40105070.txt b/fastlane/metadata/android/en-US/changelogs/40105070.txt new file mode 100644 index 0000000000..8269f7145c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/40105070.txt @@ -0,0 +1,2 @@ +Main changes in this version: new UI for selecting an attachment. +Full changelog: https://github.com/vector-im/element-android/releases diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt index 77c5649709..e0919c52e3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/filter/FilterFactory.kt @@ -67,7 +67,9 @@ internal object FilterFactory { } private fun createElementTimelineFilter(): RoomEventFilter? { - return RoomEventFilter(enableUnreadThreadNotifications = true) +// we need to check if homeserver supports thread notifications before setting this param +// return RoomEventFilter(enableUnreadThreadNotifications = true) + return null } private fun createElementStateFilter(): RoomEventFilter { diff --git a/vector/build.gradle b/vector/build.gradle index 9857f88479..1ce78520f2 100644 --- a/vector/build.gradle +++ b/vector/build.gradle @@ -132,7 +132,7 @@ dependencies { implementation libs.androidx.biometric api "org.threeten:threetenbp:1.4.0:no-tzdb" - api "com.gabrielittner.threetenbp:lazythreetenbp:0.11.0" + api "com.gabrielittner.threetenbp:lazythreetenbp:0.12.0" implementation libs.squareup.moshi kapt libs.squareup.moshiKotlin diff --git a/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt b/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt index 28732c9a42..01720453ce 100644 --- a/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt +++ b/vector/src/main/java/im/vector/app/features/analytics/plan/UserProperties.kt @@ -24,26 +24,6 @@ package im.vector.app.features.analytics.plan * definition. These properties must all be device independent. */ data class UserProperties( - /** - * Whether the user has the favourites space enabled. - */ - val webMetaSpaceFavouritesEnabled: Boolean? = null, - /** - * Whether the user has the home space set to all rooms. - */ - val webMetaSpaceHomeAllRooms: Boolean? = null, - /** - * Whether the user has the home space enabled. - */ - val webMetaSpaceHomeEnabled: Boolean? = null, - /** - * Whether the user has the other rooms space enabled. - */ - val webMetaSpaceOrphansEnabled: Boolean? = null, - /** - * Whether the user has the people space enabled. - */ - val webMetaSpacePeopleEnabled: Boolean? = null, /** * The active filter in the All Chats screen. */ @@ -109,11 +89,6 @@ data class UserProperties( fun getProperties(): Map? { return mutableMapOf().apply { - webMetaSpaceFavouritesEnabled?.let { put("WebMetaSpaceFavouritesEnabled", it) } - webMetaSpaceHomeAllRooms?.let { put("WebMetaSpaceHomeAllRooms", it) } - webMetaSpaceHomeEnabled?.let { put("WebMetaSpaceHomeEnabled", it) } - webMetaSpaceOrphansEnabled?.let { put("WebMetaSpaceOrphansEnabled", it) } - webMetaSpacePeopleEnabled?.let { put("WebMetaSpacePeopleEnabled", it) } allChatsActiveFilter?.let { put("allChatsActiveFilter", it.name) } ftueUseCaseSelection?.let { put("ftueUseCaseSelection", it.name) } numFavouriteRooms?.let { put("numFavouriteRooms", it) } diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt index 120e5e22cb..60dd1320d3 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/TimelineFragment.kt @@ -1169,6 +1169,9 @@ class TimelineFragment : lazyLoadedViews.inviteView(false)?.isVisible = false if (mainState.tombstoneEvent == null) { + views.composerContainer.isInvisible = !messageComposerState.isComposerVisible + views.voiceMessageRecorderContainer.isVisible = messageComposerState.isVoiceMessageRecorderVisible + when (messageComposerState.canSendMessage) { CanSendStatus.Allowed -> { NotificationAreaView.State.Hidden @@ -1224,6 +1227,7 @@ class TimelineFragment : private fun FragmentTimelineBinding.hideComposerViews() { composerContainer.isVisible = false + voiceMessageRecorderContainer.isVisible = false } private fun renderTypingMessageNotification(roomSummary: RoomSummary?, state: RoomDetailViewState) { diff --git a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt index 85cfb76ff7..f6e10a6df9 100644 --- a/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt +++ b/vector/src/main/java/im/vector/app/features/html/PillsPostProcessor.kt @@ -83,6 +83,20 @@ class PillsPostProcessor @AssistedInject constructor( val pillSpan = linkSpan.createPillSpan(roomId) ?: return@forEach val startSpan = renderedText.getSpanStart(linkSpan) val endSpan = renderedText.getSpanEnd(linkSpan) + // GlideImagesPlugin causes duplicated pills if we have a nested spans in the pill span, + // such as images or italic text. + // Accordingly, it's better to remove all spans that are contained in this span before rendering. + renderedText.getSpans(startSpan, endSpan, Any::class.java).forEach remove@{ + if (it !is LinkSpan) { + // Make sure to only remove spans that are contained in this link, and not are bigger than this link, e.g. like reply-blocks + val start = renderedText.getSpanStart(it) + if (start < startSpan) return@remove + val end = renderedText.getSpanEnd(it) + if (end > endSpan) return@remove + + renderedText.removeSpan(it) + } + } addPillSpan(renderedText, pillSpan, startSpan, endSpan) } } diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt index bf42867b38..b0ca32016f 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/DevicesViewModel.kt @@ -31,20 +31,16 @@ import im.vector.app.features.auth.PendingAuthHandler import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult +import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase import im.vector.app.features.settings.devices.v2.verification.GetCurrentSessionCrossSigningInfoUseCase import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth import timber.log.Timber -import kotlin.coroutines.Continuation class DevicesViewModel @AssistedInject constructor( @Assisted initialState: DevicesViewState, @@ -166,16 +162,14 @@ class DevicesViewModel @AssistedInject constructor( if (deviceIds.isEmpty()) { return@launch } - val signoutResult = signout(deviceIds) + val result = signout(deviceIds) setLoading(false) - if (signoutResult.isSuccess) { + val error = result.exceptionOrNull() + if (error == null) { onSignoutSuccess() } else { - when (val failure = signoutResult.exceptionOrNull()) { - null -> onSignoutSuccess() - else -> onSignoutFailure(failure) - } + onSignoutFailure(error) } } } @@ -187,16 +181,9 @@ class DevicesViewModel @AssistedInject constructor( .orEmpty() } - private suspend fun signout(deviceIds: List) = signoutSessionsUseCase.execute(deviceIds, object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) { - is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result) - is SignoutSessionResult.Completed -> Unit - } - } - }) + private suspend fun signout(deviceIds: List) = signoutSessionsUseCase.execute(deviceIds, this::onReAuthNeeded) - private fun onReAuthNeeded(reAuthNeeded: SignoutSessionResult.ReAuthNeeded) { + private fun onReAuthNeeded(reAuthNeeded: SignoutSessionsReAuthNeeded) { Timber.d("onReAuthNeeded") pendingAuthHandler.pendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) pendingAuthHandler.uiaContinuation = reAuthNeeded.uiaContinuation diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt index 7cca2e54e1..fb20f0fd31 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModel.kt @@ -33,24 +33,18 @@ import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType -import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult +import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase import kotlinx.coroutines.Job import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth import timber.log.Timber -import kotlin.coroutines.Continuation class OtherSessionsViewModel @AssistedInject constructor( @Assisted private val initialState: OtherSessionsViewState, activeSessionHolder: ActiveSessionHolder, private val getDeviceFullInfoListUseCase: GetDeviceFullInfoListUseCase, private val signoutSessionsUseCase: SignoutSessionsUseCase, - private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, private val pendingAuthHandler: PendingAuthHandler, refreshDevicesUseCase: RefreshDevicesUseCase, @DefaultPreferences @@ -193,16 +187,14 @@ class OtherSessionsViewModel @AssistedInject constructor( if (deviceIds.isEmpty()) { return@launch } - val signoutResult = signout(deviceIds) + val result = signout(deviceIds) setLoading(false) - if (signoutResult.isSuccess) { + val error = result.exceptionOrNull() + if (error == null) { onSignoutSuccess() } else { - when (val failure = signoutResult.exceptionOrNull()) { - null -> onSignoutSuccess() - else -> onSignoutFailure(failure) - } + onSignoutFailure(error) } } } @@ -215,16 +207,9 @@ class OtherSessionsViewModel @AssistedInject constructor( }.mapNotNull { it.deviceInfo.deviceId } } - private suspend fun signout(deviceIds: List) = signoutSessionsUseCase.execute(deviceIds, object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) { - is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result) - is SignoutSessionResult.Completed -> Unit - } - } - }) + private suspend fun signout(deviceIds: List) = signoutSessionsUseCase.execute(deviceIds, this::onReAuthNeeded) - private fun onReAuthNeeded(reAuthNeeded: SignoutSessionResult.ReAuthNeeded) { + private fun onReAuthNeeded(reAuthNeeded: SignoutSessionsReAuthNeeded) { Timber.d("onReAuthNeeded") pendingAuthHandler.pendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) pendingAuthHandler.uiaContinuation = reAuthNeeded.uiaContinuation diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt index a344474eba..e5d2ac1d5a 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModel.kt @@ -34,28 +34,24 @@ import im.vector.app.features.settings.devices.v2.VectorSessionsListViewModel import im.vector.app.features.settings.devices.v2.notification.GetNotificationsStatusUseCase import im.vector.app.features.settings.devices.v2.notification.TogglePushNotificationUseCase import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionUseCase +import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded +import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSessionCanBeVerifiedUseCase import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel import org.matrix.android.sdk.api.session.uia.DefaultBaseAuth import timber.log.Timber -import kotlin.coroutines.Continuation class SessionOverviewViewModel @AssistedInject constructor( @Assisted val initialState: SessionOverviewViewState, private val getDeviceFullInfoUseCase: GetDeviceFullInfoUseCase, private val checkIfCurrentSessionCanBeVerifiedUseCase: CheckIfCurrentSessionCanBeVerifiedUseCase, - private val signoutSessionUseCase: SignoutSessionUseCase, + private val signoutSessionsUseCase: SignoutSessionsUseCase, private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, private val pendingAuthHandler: PendingAuthHandler, private val activeSessionHolder: ActiveSessionHolder, @@ -174,30 +170,21 @@ class SessionOverviewViewModel @AssistedInject constructor( private fun handleSignoutOtherSession(deviceId: String) { viewModelScope.launch { setLoading(true) - val signoutResult = signout(deviceId) + val result = signout(deviceId) setLoading(false) - if (signoutResult.isSuccess) { + val error = result.exceptionOrNull() + if (error == null) { onSignoutSuccess() } else { - when (val failure = signoutResult.exceptionOrNull()) { - null -> onSignoutSuccess() - else -> onSignoutFailure(failure) - } + onSignoutFailure(error) } } } - private suspend fun signout(deviceId: String) = signoutSessionUseCase.execute(deviceId, object : UserInteractiveAuthInterceptor { - override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { - when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) { - is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result) - is SignoutSessionResult.Completed -> Unit - } - } - }) + private suspend fun signout(deviceId: String) = signoutSessionsUseCase.execute(listOf(deviceId), this::onReAuthNeeded) - private fun onReAuthNeeded(reAuthNeeded: SignoutSessionResult.ReAuthNeeded) { + private fun onReAuthNeeded(reAuthNeeded: SignoutSessionsReAuthNeeded) { Timber.d("onReAuthNeeded") pendingAuthHandler.pendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) pendingAuthHandler.uiaContinuation = reAuthNeeded.uiaContinuation diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt index 4316995272..42ebd7782e 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCase.kt @@ -37,17 +37,16 @@ class InterceptSignoutFlowResponseUseCase @Inject constructor( flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation - ): SignoutSessionResult { + ): SignoutSessionsReAuthNeeded? { return if (flowResponse.nextUncompletedStage() == LoginFlowTypes.PASSWORD && reAuthHelper.data != null && errCode == null) { UserPasswordAuth( session = null, user = activeSessionHolder.getActiveSession().myUserId, password = reAuthHelper.data ).let { promise.resume(it) } - - SignoutSessionResult.Completed + null } else { - SignoutSessionResult.ReAuthNeeded( + SignoutSessionsReAuthNeeded( pendingAuth = DefaultBaseAuth(session = flowResponse.session), uiaContinuation = promise, flowResponse = flowResponse, diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt deleted file mode 100644 index bc6cff0d43..0000000000 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCase.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 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 - * - * http://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 im.vector.app.features.settings.devices.v2.signout - -import im.vector.app.core.di.ActiveSessionHolder -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.util.awaitCallback -import javax.inject.Inject - -/** - * Use case to signout a single session. - */ -class SignoutSessionUseCase @Inject constructor( - private val activeSessionHolder: ActiveSessionHolder, -) { - - suspend fun execute(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor): Result { - return deleteDevice(deviceId, userInteractiveAuthInterceptor) - } - - private suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = runCatching { - awaitCallback { matrixCallback -> - activeSessionHolder.getActiveSession() - .cryptoService() - .deleteDevice(deviceId, userInteractiveAuthInterceptor, matrixCallback) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsReAuthNeeded.kt similarity index 71% rename from vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt rename to vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsReAuthNeeded.kt index fa1fb31b66..56e3d17686 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionResult.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsReAuthNeeded.kt @@ -20,13 +20,9 @@ import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import kotlin.coroutines.Continuation -sealed class SignoutSessionResult { - data class ReAuthNeeded( - val pendingAuth: UIABaseAuth, - val uiaContinuation: Continuation, - val flowResponse: RegistrationFlowResponse, - val errCode: String? - ) : SignoutSessionResult() - - object Completed : SignoutSessionResult() -} +data class SignoutSessionsReAuthNeeded( + val pendingAuth: UIABaseAuth, + val uiaContinuation: Continuation, + val flowResponse: RegistrationFlowResponse, + val errCode: String? +) diff --git a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCase.kt b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCase.kt index b4fc78043e..1cf713a711 100644 --- a/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCase.kt @@ -16,27 +16,42 @@ package im.vector.app.features.settings.devices.v2.signout +import androidx.annotation.Size import im.vector.app.core.di.ActiveSessionHolder +import org.matrix.android.sdk.api.auth.UIABaseAuth import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor +import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.util.awaitCallback +import timber.log.Timber import javax.inject.Inject +import kotlin.coroutines.Continuation -/** - * Use case to signout several sessions. - */ class SignoutSessionsUseCase @Inject constructor( private val activeSessionHolder: ActiveSessionHolder, + private val interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, ) { - suspend fun execute(deviceIds: List, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor): Result { - return deleteDevices(deviceIds, userInteractiveAuthInterceptor) + suspend fun execute( + @Size(min = 1) deviceIds: List, + onReAuthNeeded: (SignoutSessionsReAuthNeeded) -> Unit, + ): Result = runCatching { + Timber.d("start execute with ${deviceIds.size} deviceIds") + + val authInterceptor = object : UserInteractiveAuthInterceptor { + override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation) { + val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise) + result?.let(onReAuthNeeded) + } + } + + deleteDevices(deviceIds, authInterceptor) + Timber.d("end execute") } - private suspend fun deleteDevices(deviceIds: List, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = runCatching { - awaitCallback { matrixCallback -> - activeSessionHolder.getActiveSession() - .cryptoService() - .deleteDevices(deviceIds, userInteractiveAuthInterceptor, matrixCallback) - } - } + private suspend fun deleteDevices(deviceIds: List, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = + awaitCallback { matrixCallback -> + activeSessionHolder.getActiveSession() + .cryptoService() + .deleteDevices(deviceIds, userInteractiveAuthInterceptor, matrixCallback) + } } diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt index 87bae3abb7..0278e15026 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/DevicesViewModelTest.kt @@ -232,7 +232,7 @@ class DevicesViewModelTest { // Given val expectedViewState = givenInitialViewState(deviceId1 = A_DEVICE_ID_1, deviceId2 = A_CURRENT_DEVICE_ID) // signout all devices except the current device - fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_DEVICE_ID_1), fakeInterceptSignoutFlowResponseUseCase) + fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_DEVICE_ID_1)) // When val viewModel = createViewModel() @@ -279,7 +279,7 @@ class DevicesViewModelTest { @Test fun `given reAuth is needed during multiSignout when handling multiSignout action then requestReAuth is sent and pending auth is stored`() { // Given - val reAuthNeeded = fakeSignoutSessionsUseCase.givenSignoutReAuthNeeded(listOf(A_DEVICE_ID_1, A_DEVICE_ID_2), fakeInterceptSignoutFlowResponseUseCase) + val reAuthNeeded = fakeSignoutSessionsUseCase.givenSignoutReAuthNeeded(listOf(A_DEVICE_ID_1, A_DEVICE_ID_2)) val expectedPendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) val expectedReAuthEvent = DevicesViewEvent.RequestReAuth(reAuthNeeded.flowResponse, reAuthNeeded.errCode) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt index e7bb14695c..d6ed5a61a7 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/othersessions/OtherSessionsViewModelTest.kt @@ -23,7 +23,6 @@ import im.vector.app.features.settings.devices.v2.DeviceFullInfo import im.vector.app.features.settings.devices.v2.GetDeviceFullInfoListUseCase import im.vector.app.features.settings.devices.v2.RefreshDevicesUseCase import im.vector.app.features.settings.devices.v2.filter.DeviceManagerFilterType -import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakePendingAuthHandler import im.vector.app.test.fakes.FakeSharedPreferences @@ -67,7 +66,6 @@ class OtherSessionsViewModelTest { private val fakeGetDeviceFullInfoListUseCase = mockk() private val fakeRefreshDevicesUseCase = mockk(relaxed = true) private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase() - private val fakeInterceptSignoutFlowResponseUseCase = mockk() private val fakePendingAuthHandler = FakePendingAuthHandler() private val fakeSharedPreferences = FakeSharedPreferences() @@ -77,7 +75,6 @@ class OtherSessionsViewModelTest { activeSessionHolder = fakeActiveSessionHolder.instance, getDeviceFullInfoListUseCase = fakeGetDeviceFullInfoListUseCase, signoutSessionsUseCase = fakeSignoutSessionsUseCase.instance, - interceptSignoutFlowResponseUseCase = fakeInterceptSignoutFlowResponseUseCase, pendingAuthHandler = fakePendingAuthHandler.instance, refreshDevicesUseCase = fakeRefreshDevicesUseCase, sharedPreferences = fakeSharedPreferences, @@ -325,7 +322,7 @@ class OtherSessionsViewModelTest { val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) // signout only selected devices - fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_DEVICE_ID_2), fakeInterceptSignoutFlowResponseUseCase) + fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_DEVICE_ID_2)) val expectedViewState = OtherSessionsViewState( devices = Success(listOf(deviceFullInfo1, deviceFullInfo2)), currentFilter = defaultArgs.defaultFilter, @@ -361,7 +358,7 @@ class OtherSessionsViewModelTest { val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) // signout all devices - fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_DEVICE_ID_1, A_DEVICE_ID_2), fakeInterceptSignoutFlowResponseUseCase) + fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_DEVICE_ID_1, A_DEVICE_ID_2)) val expectedViewState = OtherSessionsViewState( devices = Success(listOf(deviceFullInfo1, deviceFullInfo2)), currentFilter = defaultArgs.defaultFilter, @@ -426,7 +423,7 @@ class OtherSessionsViewModelTest { val deviceFullInfo2 = aDeviceFullInfo(A_DEVICE_ID_2, isSelected = true) val devices: List = listOf(deviceFullInfo1, deviceFullInfo2) givenGetDeviceFullInfoListReturns(filterType = defaultArgs.defaultFilter, devices) - val reAuthNeeded = fakeSignoutSessionsUseCase.givenSignoutReAuthNeeded(listOf(A_DEVICE_ID_1, A_DEVICE_ID_2), fakeInterceptSignoutFlowResponseUseCase) + val reAuthNeeded = fakeSignoutSessionsUseCase.givenSignoutReAuthNeeded(listOf(A_DEVICE_ID_1, A_DEVICE_ID_2)) val expectedPendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) val expectedReAuthEvent = OtherSessionsViewEvents.RequestReAuth(reAuthNeeded.flowResponse, reAuthNeeded.errCode) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt index aec555d8eb..635beba833 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt @@ -29,7 +29,7 @@ import im.vector.app.features.settings.devices.v2.verification.CheckIfCurrentSes import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakePendingAuthHandler import im.vector.app.test.fakes.FakeSharedPreferences -import im.vector.app.test.fakes.FakeSignoutSessionUseCase +import im.vector.app.test.fakes.FakeSignoutSessionsUseCase import im.vector.app.test.fakes.FakeTogglePushNotificationUseCase import im.vector.app.test.fakes.FakeVerificationService import im.vector.app.test.test @@ -71,7 +71,7 @@ class SessionOverviewViewModelTest { private val getDeviceFullInfoUseCase = mockk(relaxed = true) private val fakeActiveSessionHolder = FakeActiveSessionHolder() private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk() - private val fakeSignoutSessionUseCase = FakeSignoutSessionUseCase() + private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase() private val interceptSignoutFlowResponseUseCase = mockk() private val fakePendingAuthHandler = FakePendingAuthHandler() private val refreshDevicesUseCase = mockk(relaxed = true) @@ -84,7 +84,7 @@ class SessionOverviewViewModelTest { initialState = SessionOverviewViewState(args), getDeviceFullInfoUseCase = getDeviceFullInfoUseCase, checkIfCurrentSessionCanBeVerifiedUseCase = checkIfCurrentSessionCanBeVerifiedUseCase, - signoutSessionUseCase = fakeSignoutSessionUseCase.instance, + signoutSessionsUseCase = fakeSignoutSessionsUseCase.instance, interceptSignoutFlowResponseUseCase = interceptSignoutFlowResponseUseCase, pendingAuthHandler = fakePendingAuthHandler.instance, activeSessionHolder = fakeActiveSessionHolder.instance, @@ -252,7 +252,7 @@ class SessionOverviewViewModelTest { val deviceFullInfo = mockk() every { deviceFullInfo.isCurrentDevice } returns false every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) - fakeSignoutSessionUseCase.givenSignoutSuccess(A_SESSION_ID_1, interceptSignoutFlowResponseUseCase) + fakeSignoutSessionsUseCase.givenSignoutSuccess(listOf(A_SESSION_ID_1)) val signoutAction = SessionOverviewAction.SignoutOtherSession givenCurrentSessionIsTrusted() val expectedViewState = SessionOverviewViewState( @@ -289,7 +289,7 @@ class SessionOverviewViewModelTest { every { deviceFullInfo.isCurrentDevice } returns false every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) val error = Exception() - fakeSignoutSessionUseCase.givenSignoutError(A_SESSION_ID_1, error) + fakeSignoutSessionsUseCase.givenSignoutError(listOf(A_SESSION_ID_1), error) val signoutAction = SessionOverviewAction.SignoutOtherSession givenCurrentSessionIsTrusted() val expectedViewState = SessionOverviewViewState( @@ -322,7 +322,7 @@ class SessionOverviewViewModelTest { val deviceFullInfo = mockk() every { deviceFullInfo.isCurrentDevice } returns false every { getDeviceFullInfoUseCase.execute(A_SESSION_ID_1) } returns flowOf(deviceFullInfo) - val reAuthNeeded = fakeSignoutSessionUseCase.givenSignoutReAuthNeeded(A_SESSION_ID_1, interceptSignoutFlowResponseUseCase) + val reAuthNeeded = fakeSignoutSessionsUseCase.givenSignoutReAuthNeeded(listOf(A_SESSION_ID_1)) val signoutAction = SessionOverviewAction.SignoutOtherSession givenCurrentSessionIsTrusted() val expectedPendingAuth = DefaultBaseAuth(session = reAuthNeeded.flowResponse.session) diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCaseTest.kt index 35551ba36e..cd0575f2a0 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/InterceptSignoutFlowResponseUseCaseTest.kt @@ -24,8 +24,8 @@ import io.mockk.mockk import io.mockk.mockkStatic import io.mockk.runs import io.mockk.unmockkAll +import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeEqualTo -import org.amshove.kluent.shouldBeInstanceOf import org.junit.After import org.junit.Before import org.junit.Test @@ -63,7 +63,7 @@ class InterceptSignoutFlowResponseUseCaseTest { } @Test - fun `given no error and a stored password and a next stage as password when intercepting then promise is resumed and success is returned`() { + fun `given no error and a stored password and a next stage as password when intercepting then promise is resumed and null is returned`() { // Given val registrationFlowResponse = givenNextUncompletedStage(LoginFlowTypes.PASSWORD, A_SESSION_ID) fakeReAuthHelper.givenStoredPassword(A_PASSWORD) @@ -84,7 +84,7 @@ class InterceptSignoutFlowResponseUseCaseTest { ) // Then - result shouldBeInstanceOf (SignoutSessionResult.Completed::class) + result shouldBe null every { promise.resume(expectedAuth) } @@ -97,7 +97,7 @@ class InterceptSignoutFlowResponseUseCaseTest { fakeReAuthHelper.givenStoredPassword(A_PASSWORD) val errorCode = AN_ERROR_CODE val promise = mockk>() - val expectedResult = SignoutSessionResult.ReAuthNeeded( + val expectedResult = SignoutSessionsReAuthNeeded( pendingAuth = DefaultBaseAuth(session = A_SESSION_ID), uiaContinuation = promise, flowResponse = registrationFlowResponse, @@ -122,7 +122,7 @@ class InterceptSignoutFlowResponseUseCaseTest { fakeReAuthHelper.givenStoredPassword(A_PASSWORD) val errorCode: String? = null val promise = mockk>() - val expectedResult = SignoutSessionResult.ReAuthNeeded( + val expectedResult = SignoutSessionsReAuthNeeded( pendingAuth = DefaultBaseAuth(session = A_SESSION_ID), uiaContinuation = promise, flowResponse = registrationFlowResponse, @@ -147,7 +147,7 @@ class InterceptSignoutFlowResponseUseCaseTest { fakeReAuthHelper.givenStoredPassword(null) val errorCode: String? = null val promise = mockk>() - val expectedResult = SignoutSessionResult.ReAuthNeeded( + val expectedResult = SignoutSessionsReAuthNeeded( pendingAuth = DefaultBaseAuth(session = A_SESSION_ID), uiaContinuation = promise, flowResponse = registrationFlowResponse, diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCaseTest.kt deleted file mode 100644 index 5af91c16ce..0000000000 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionUseCaseTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2022 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 - * - * http://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 im.vector.app.features.settings.devices.v2.signout - -import im.vector.app.test.fakes.FakeActiveSessionHolder -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.test.runTest -import org.amshove.kluent.shouldBe -import org.junit.Test -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor - -private const val A_DEVICE_ID = "device-id" - -class SignoutSessionUseCaseTest { - - private val fakeActiveSessionHolder = FakeActiveSessionHolder() - - private val signoutSessionUseCase = SignoutSessionUseCase( - activeSessionHolder = fakeActiveSessionHolder.instance - ) - - @Test - fun `given a device id when signing out with success then success result is returned`() = runTest { - // Given - val interceptor = givenAuthInterceptor() - fakeActiveSessionHolder.fakeSession - .fakeCryptoService - .givenDeleteDeviceSucceeds(A_DEVICE_ID) - - // When - val result = signoutSessionUseCase.execute(A_DEVICE_ID, interceptor) - - // Then - result.isSuccess shouldBe true - every { - fakeActiveSessionHolder.fakeSession - .fakeCryptoService - .deleteDevice(A_DEVICE_ID, interceptor, any()) - } - } - - @Test - fun `given a device id when signing out with error then failure result is returned`() = runTest { - // Given - val interceptor = givenAuthInterceptor() - val error = mockk() - fakeActiveSessionHolder.fakeSession - .fakeCryptoService - .givenDeleteDeviceFailsWithError(A_DEVICE_ID, error) - - // When - val result = signoutSessionUseCase.execute(A_DEVICE_ID, interceptor) - - // Then - result.isFailure shouldBe true - every { - fakeActiveSessionHolder.fakeSession - .fakeCryptoService - .deleteDevice(A_DEVICE_ID, interceptor, any()) - } - } - - private fun givenAuthInterceptor() = mockk() -} diff --git a/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCaseTest.kt b/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCaseTest.kt index 08a9fa625b..70d2b4b039 100644 --- a/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCaseTest.kt +++ b/vector/src/test/java/im/vector/app/features/settings/devices/v2/signout/SignoutSessionsUseCaseTest.kt @@ -19,10 +19,10 @@ package im.vector.app.features.settings.devices.v2.signout import im.vector.app.test.fakes.FakeActiveSessionHolder import io.mockk.every import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe import org.junit.Test -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor private const val A_DEVICE_ID_1 = "device-id-1" private const val A_DEVICE_ID_2 = "device-id-2" @@ -30,36 +30,38 @@ private const val A_DEVICE_ID_2 = "device-id-2" class SignoutSessionsUseCaseTest { private val fakeActiveSessionHolder = FakeActiveSessionHolder() + private val fakeInterceptSignoutFlowResponseUseCase = mockk() private val signoutSessionsUseCase = SignoutSessionsUseCase( - activeSessionHolder = fakeActiveSessionHolder.instance + activeSessionHolder = fakeActiveSessionHolder.instance, + interceptSignoutFlowResponseUseCase = fakeInterceptSignoutFlowResponseUseCase, ) @Test fun `given a list of device ids when signing out with success then success result is returned`() = runTest { // Given - val interceptor = givenAuthInterceptor() + val callback = givenOnReAuthCallback() val deviceIds = listOf(A_DEVICE_ID_1, A_DEVICE_ID_2) fakeActiveSessionHolder.fakeSession .fakeCryptoService .givenDeleteDevicesSucceeds(deviceIds) // When - val result = signoutSessionsUseCase.execute(deviceIds, interceptor) + val result = signoutSessionsUseCase.execute(deviceIds, callback) // Then result.isSuccess shouldBe true - every { + verify { fakeActiveSessionHolder.fakeSession .fakeCryptoService - .deleteDevices(deviceIds, interceptor, any()) + .deleteDevices(deviceIds, any(), any()) } } @Test fun `given a list of device ids when signing out with error then failure result is returned`() = runTest { // Given - val interceptor = givenAuthInterceptor() + val interceptor = givenOnReAuthCallback() val deviceIds = listOf(A_DEVICE_ID_1, A_DEVICE_ID_2) val error = mockk() fakeActiveSessionHolder.fakeSession @@ -71,12 +73,41 @@ class SignoutSessionsUseCaseTest { // Then result.isFailure shouldBe true - every { + verify { fakeActiveSessionHolder.fakeSession .fakeCryptoService - .deleteDevices(deviceIds, interceptor, any()) + .deleteDevices(deviceIds, any(), any()) } } - private fun givenAuthInterceptor() = mockk() + @Test + fun `given a list of device ids when signing out with reAuth needed then callback is called`() = runTest { + // Given + val callback = givenOnReAuthCallback() + val deviceIds = listOf(A_DEVICE_ID_1, A_DEVICE_ID_2) + fakeActiveSessionHolder.fakeSession + .fakeCryptoService + .givenDeleteDevicesNeedsUIAuth(deviceIds) + val reAuthNeeded = SignoutSessionsReAuthNeeded( + pendingAuth = mockk(), + uiaContinuation = mockk(), + flowResponse = mockk(), + errCode = "errorCode" + ) + every { fakeInterceptSignoutFlowResponseUseCase.execute(any(), any(), any()) } returns reAuthNeeded + + // When + val result = signoutSessionsUseCase.execute(deviceIds, callback) + + // Then + result.isSuccess shouldBe true + verify { + fakeActiveSessionHolder.fakeSession + .fakeCryptoService + .deleteDevices(deviceIds, any(), any()) + callback(reAuthNeeded) + } + } + + private fun givenOnReAuthCallback(): (SignoutSessionsReAuthNeeded) -> Unit = {} } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt index 5f34c45fa7..b23f018cf5 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeCryptoService.kt @@ -22,6 +22,7 @@ import io.mockk.every import io.mockk.mockk import io.mockk.slot import org.matrix.android.sdk.api.MatrixCallback +import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.session.crypto.CryptoService import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo @@ -70,30 +71,21 @@ class FakeCryptoService( } } - fun givenDeleteDeviceSucceeds(deviceId: String) { - val matrixCallback = slot>() - every { deleteDevice(deviceId, any(), capture(matrixCallback)) } answers { + fun givenDeleteDevicesSucceeds(deviceIds: List) { + every { deleteDevices(deviceIds, any(), any()) } answers { thirdArg>().onSuccess(Unit) } } - fun givenDeleteDeviceFailsWithError(deviceId: String, error: Exception) { - val matrixCallback = slot>() - every { deleteDevice(deviceId, any(), capture(matrixCallback)) } answers { - thirdArg>().onFailure(error) - } - } - - fun givenDeleteDevicesSucceeds(deviceIds: List) { - val matrixCallback = slot>() - every { deleteDevices(deviceIds, any(), capture(matrixCallback)) } answers { + fun givenDeleteDevicesNeedsUIAuth(deviceIds: List) { + every { deleteDevices(deviceIds, any(), any()) } answers { + secondArg().performStage(mockk(), "", mockk()) thirdArg>().onSuccess(Unit) } } fun givenDeleteDevicesFailsWithError(deviceIds: List, error: Exception) { - val matrixCallback = slot>() - every { deleteDevices(deviceIds, any(), capture(matrixCallback)) } answers { + every { deleteDevices(deviceIds, any(), any()) } answers { thirdArg>().onFailure(error) } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionUseCase.kt deleted file mode 100644 index 8a6b101ff6..0000000000 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionUseCase.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2022 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 - * - * http://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 im.vector.app.test.fakes - -import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionUseCase -import io.mockk.coEvery -import io.mockk.every -import io.mockk.mockk -import io.mockk.slot -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor -import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import kotlin.coroutines.Continuation - -class FakeSignoutSessionUseCase { - - val instance = mockk() - - fun givenSignoutSuccess( - deviceId: String, - interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, - ) { - val interceptor = slot() - val flowResponse = mockk() - val errorCode = "errorCode" - val promise = mockk>() - every { interceptSignoutFlowResponseUseCase.execute(flowResponse, errorCode, promise) } returns SignoutSessionResult.Completed - coEvery { instance.execute(deviceId, capture(interceptor)) } coAnswers { - secondArg().performStage(flowResponse, errorCode, promise) - Result.success(Unit) - } - } - - fun givenSignoutReAuthNeeded( - deviceId: String, - interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, - ): SignoutSessionResult.ReAuthNeeded { - val interceptor = slot() - val flowResponse = mockk() - every { flowResponse.session } returns "a-session-id" - val errorCode = "errorCode" - val promise = mockk>() - val reAuthNeeded = SignoutSessionResult.ReAuthNeeded( - pendingAuth = mockk(), - uiaContinuation = promise, - flowResponse = flowResponse, - errCode = errorCode, - ) - every { interceptSignoutFlowResponseUseCase.execute(flowResponse, errorCode, promise) } returns reAuthNeeded - coEvery { instance.execute(deviceId, capture(interceptor)) } coAnswers { - secondArg().performStage(flowResponse, errorCode, promise) - Result.success(Unit) - } - - return reAuthNeeded - } - - fun givenSignoutError(deviceId: String, error: Throwable) { - coEvery { instance.execute(deviceId, any()) } returns Result.failure(error) - } -} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionsUseCase.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionsUseCase.kt index 04d05b1d8a..9eb3676475 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionsUseCase.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSignoutSessionsUseCase.kt @@ -16,55 +16,33 @@ package im.vector.app.test.fakes -import im.vector.app.features.settings.devices.v2.signout.InterceptSignoutFlowResponseUseCase -import im.vector.app.features.settings.devices.v2.signout.SignoutSessionResult +import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsReAuthNeeded import im.vector.app.features.settings.devices.v2.signout.SignoutSessionsUseCase import io.mockk.coEvery import io.mockk.every import io.mockk.mockk -import io.mockk.slot -import org.matrix.android.sdk.api.auth.UIABaseAuth -import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse -import kotlin.coroutines.Continuation class FakeSignoutSessionsUseCase { val instance = mockk() - fun givenSignoutSuccess( - deviceIds: List, - interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, - ) { - val interceptor = slot() - val flowResponse = mockk() - val errorCode = "errorCode" - val promise = mockk>() - every { interceptSignoutFlowResponseUseCase.execute(flowResponse, errorCode, promise) } returns SignoutSessionResult.Completed - coEvery { instance.execute(deviceIds, capture(interceptor)) } coAnswers { - secondArg().performStage(flowResponse, errorCode, promise) - Result.success(Unit) - } + fun givenSignoutSuccess(deviceIds: List) { + coEvery { instance.execute(deviceIds, any()) } returns Result.success(Unit) } - fun givenSignoutReAuthNeeded( - deviceIds: List, - interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, - ): SignoutSessionResult.ReAuthNeeded { - val interceptor = slot() + fun givenSignoutReAuthNeeded(deviceIds: List): SignoutSessionsReAuthNeeded { val flowResponse = mockk() every { flowResponse.session } returns "a-session-id" val errorCode = "errorCode" - val promise = mockk>() - val reAuthNeeded = SignoutSessionResult.ReAuthNeeded( + val reAuthNeeded = SignoutSessionsReAuthNeeded( pendingAuth = mockk(), - uiaContinuation = promise, + uiaContinuation = mockk(), flowResponse = flowResponse, errCode = errorCode, ) - every { interceptSignoutFlowResponseUseCase.execute(flowResponse, errorCode, promise) } returns reAuthNeeded - coEvery { instance.execute(deviceIds, capture(interceptor)) } coAnswers { - secondArg().performStage(flowResponse, errorCode, promise) + coEvery { instance.execute(deviceIds, any()) } coAnswers { + secondArg<(SignoutSessionsReAuthNeeded) -> Unit>().invoke(reAuthNeeded) Result.success(Unit) }