Merge branch 'develop' into feature/ons/toggle_ip_address_visibility
# Conflicts: # vector/src/test/java/im/vector/app/features/settings/devices/v2/overview/SessionOverviewViewModelTest.kt
This commit is contained in:
		
						commit
						b81fc4f8f1
					
				| @ -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 | ||||
|                 } | ||||
|               } | ||||
|  | ||||
| @ -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) | ||||
| ====================================== | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										1
									
								
								changelog.d/7501.bugfix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changelog.d/7501.bugfix
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| Fix duplicated mention pills in some cases | ||||
							
								
								
									
										1
									
								
								changelog.d/7509.bugfix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								changelog.d/7509.bugfix
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | ||||
| When joining a room, the message composer is displayed once the room is loaded. | ||||
| @ -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 | ||||
|  | ||||
							
								
								
									
										2
									
								
								fastlane/metadata/android/en-US/changelogs/40105070.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								fastlane/metadata/android/en-US/changelogs/40105070.txt
									
									
									
									
									
										Normal file
									
								
							| @ -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 | ||||
| @ -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 { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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<String, Any>? { | ||||
|         return mutableMapOf<String, Any>().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) } | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -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<String>) = signoutSessionsUseCase.execute(deviceIds, object : UserInteractiveAuthInterceptor { | ||||
|         override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { | ||||
|             when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) { | ||||
|                 is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result) | ||||
|                 is SignoutSessionResult.Completed -> Unit | ||||
|             } | ||||
|         } | ||||
|     }) | ||||
|     private suspend fun signout(deviceIds: List<String>) = 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 | ||||
|  | ||||
| @ -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<String>) = signoutSessionsUseCase.execute(deviceIds, object : UserInteractiveAuthInterceptor { | ||||
|         override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { | ||||
|             when (val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise)) { | ||||
|                 is SignoutSessionResult.ReAuthNeeded -> onReAuthNeeded(result) | ||||
|                 is SignoutSessionResult.Completed -> Unit | ||||
|             } | ||||
|         } | ||||
|     }) | ||||
|     private suspend fun signout(deviceIds: List<String>) = 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 | ||||
|  | ||||
| @ -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<UIABaseAuth>) { | ||||
|             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 | ||||
|  | ||||
| @ -37,17 +37,16 @@ class InterceptSignoutFlowResponseUseCase @Inject constructor( | ||||
|             flowResponse: RegistrationFlowResponse, | ||||
|             errCode: String?, | ||||
|             promise: Continuation<UIABaseAuth> | ||||
|     ): 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, | ||||
|  | ||||
| @ -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<Unit> { | ||||
|         return deleteDevice(deviceId, userInteractiveAuthInterceptor) | ||||
|     } | ||||
| 
 | ||||
|     private suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = runCatching { | ||||
|         awaitCallback { matrixCallback -> | ||||
|             activeSessionHolder.getActiveSession() | ||||
|                     .cryptoService() | ||||
|                     .deleteDevice(deviceId, userInteractiveAuthInterceptor, matrixCallback) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -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<UIABaseAuth>, | ||||
|             val flowResponse: RegistrationFlowResponse, | ||||
|             val errCode: String? | ||||
|     ) : SignoutSessionResult() | ||||
| 
 | ||||
|     object Completed : SignoutSessionResult() | ||||
| } | ||||
| data class SignoutSessionsReAuthNeeded( | ||||
|         val pendingAuth: UIABaseAuth, | ||||
|         val uiaContinuation: Continuation<UIABaseAuth>, | ||||
|         val flowResponse: RegistrationFlowResponse, | ||||
|         val errCode: String? | ||||
| ) | ||||
| @ -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<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor): Result<Unit> { | ||||
|         return deleteDevices(deviceIds, userInteractiveAuthInterceptor) | ||||
|     suspend fun execute( | ||||
|             @Size(min = 1) deviceIds: List<String>, | ||||
|             onReAuthNeeded: (SignoutSessionsReAuthNeeded) -> Unit, | ||||
|     ): Result<Unit> = runCatching { | ||||
|         Timber.d("start execute with ${deviceIds.size} deviceIds") | ||||
| 
 | ||||
|         val authInterceptor = object : UserInteractiveAuthInterceptor { | ||||
|             override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { | ||||
|                 val result = interceptSignoutFlowResponseUseCase.execute(flowResponse, errCode, promise) | ||||
|                 result?.let(onReAuthNeeded) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         deleteDevices(deviceIds, authInterceptor) | ||||
|         Timber.d("end execute") | ||||
|     } | ||||
| 
 | ||||
|     private suspend fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = runCatching { | ||||
|         awaitCallback { matrixCallback -> | ||||
|             activeSessionHolder.getActiveSession() | ||||
|                     .cryptoService() | ||||
|                     .deleteDevices(deviceIds, userInteractiveAuthInterceptor, matrixCallback) | ||||
|         } | ||||
|     } | ||||
|     private suspend fun deleteDevices(deviceIds: List<String>, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) = | ||||
|             awaitCallback { matrixCallback -> | ||||
|                 activeSessionHolder.getActiveSession() | ||||
|                         .cryptoService() | ||||
|                         .deleteDevices(deviceIds, userInteractiveAuthInterceptor, matrixCallback) | ||||
|             } | ||||
| } | ||||
|  | ||||
| @ -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) | ||||
| 
 | ||||
|  | ||||
| @ -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<GetDeviceFullInfoListUseCase>() | ||||
|     private val fakeRefreshDevicesUseCase = mockk<RefreshDevicesUseCase>(relaxed = true) | ||||
|     private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase() | ||||
|     private val fakeInterceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>() | ||||
|     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<DeviceFullInfo> = 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<DeviceFullInfo> = 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<DeviceFullInfo> = 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) | ||||
| 
 | ||||
|  | ||||
| @ -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<GetDeviceFullInfoUseCase>(relaxed = true) | ||||
|     private val fakeActiveSessionHolder = FakeActiveSessionHolder() | ||||
|     private val checkIfCurrentSessionCanBeVerifiedUseCase = mockk<CheckIfCurrentSessionCanBeVerifiedUseCase>() | ||||
|     private val fakeSignoutSessionUseCase = FakeSignoutSessionUseCase() | ||||
|     private val fakeSignoutSessionsUseCase = FakeSignoutSessionsUseCase() | ||||
|     private val interceptSignoutFlowResponseUseCase = mockk<InterceptSignoutFlowResponseUseCase>() | ||||
|     private val fakePendingAuthHandler = FakePendingAuthHandler() | ||||
|     private val refreshDevicesUseCase = mockk<RefreshDevicesUseCase>(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<DeviceFullInfo>() | ||||
|         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<DeviceFullInfo>() | ||||
|         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) | ||||
|  | ||||
| @ -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<Continuation<UIABaseAuth>>() | ||||
|         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<Continuation<UIABaseAuth>>() | ||||
|         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<Continuation<UIABaseAuth>>() | ||||
|         val expectedResult = SignoutSessionResult.ReAuthNeeded( | ||||
|         val expectedResult = SignoutSessionsReAuthNeeded( | ||||
|                 pendingAuth = DefaultBaseAuth(session = A_SESSION_ID), | ||||
|                 uiaContinuation = promise, | ||||
|                 flowResponse = registrationFlowResponse, | ||||
|  | ||||
| @ -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<Exception>() | ||||
|         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<UserInteractiveAuthInterceptor>() | ||||
| } | ||||
| @ -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<InterceptSignoutFlowResponseUseCase>() | ||||
| 
 | ||||
|     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<Exception>() | ||||
|         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<UserInteractiveAuthInterceptor>() | ||||
|     @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 = {} | ||||
| } | ||||
|  | ||||
| @ -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<MatrixCallback<Unit>>() | ||||
|         every { deleteDevice(deviceId, any(), capture(matrixCallback)) } answers { | ||||
|     fun givenDeleteDevicesSucceeds(deviceIds: List<String>) { | ||||
|         every { deleteDevices(deviceIds, any(), any()) } answers { | ||||
|             thirdArg<MatrixCallback<Unit>>().onSuccess(Unit) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun givenDeleteDeviceFailsWithError(deviceId: String, error: Exception) { | ||||
|         val matrixCallback = slot<MatrixCallback<Unit>>() | ||||
|         every { deleteDevice(deviceId, any(), capture(matrixCallback)) } answers { | ||||
|             thirdArg<MatrixCallback<Unit>>().onFailure(error) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun givenDeleteDevicesSucceeds(deviceIds: List<String>) { | ||||
|         val matrixCallback = slot<MatrixCallback<Unit>>() | ||||
|         every { deleteDevices(deviceIds, any(), capture(matrixCallback)) } answers { | ||||
|     fun givenDeleteDevicesNeedsUIAuth(deviceIds: List<String>) { | ||||
|         every { deleteDevices(deviceIds, any(), any()) } answers { | ||||
|             secondArg<UserInteractiveAuthInterceptor>().performStage(mockk(), "", mockk()) | ||||
|             thirdArg<MatrixCallback<Unit>>().onSuccess(Unit) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun givenDeleteDevicesFailsWithError(deviceIds: List<String>, error: Exception) { | ||||
|         val matrixCallback = slot<MatrixCallback<Unit>>() | ||||
|         every { deleteDevices(deviceIds, any(), capture(matrixCallback)) } answers { | ||||
|         every { deleteDevices(deviceIds, any(), any()) } answers { | ||||
|             thirdArg<MatrixCallback<Unit>>().onFailure(error) | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -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<SignoutSessionUseCase>() | ||||
| 
 | ||||
|     fun givenSignoutSuccess( | ||||
|             deviceId: String, | ||||
|             interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, | ||||
|     ) { | ||||
|         val interceptor = slot<UserInteractiveAuthInterceptor>() | ||||
|         val flowResponse = mockk<RegistrationFlowResponse>() | ||||
|         val errorCode = "errorCode" | ||||
|         val promise = mockk<Continuation<UIABaseAuth>>() | ||||
|         every { interceptSignoutFlowResponseUseCase.execute(flowResponse, errorCode, promise) } returns SignoutSessionResult.Completed | ||||
|         coEvery { instance.execute(deviceId, capture(interceptor)) } coAnswers { | ||||
|             secondArg<UserInteractiveAuthInterceptor>().performStage(flowResponse, errorCode, promise) | ||||
|             Result.success(Unit) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun givenSignoutReAuthNeeded( | ||||
|             deviceId: String, | ||||
|             interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, | ||||
|     ): SignoutSessionResult.ReAuthNeeded { | ||||
|         val interceptor = slot<UserInteractiveAuthInterceptor>() | ||||
|         val flowResponse = mockk<RegistrationFlowResponse>() | ||||
|         every { flowResponse.session } returns "a-session-id" | ||||
|         val errorCode = "errorCode" | ||||
|         val promise = mockk<Continuation<UIABaseAuth>>() | ||||
|         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<UserInteractiveAuthInterceptor>().performStage(flowResponse, errorCode, promise) | ||||
|             Result.success(Unit) | ||||
|         } | ||||
| 
 | ||||
|         return reAuthNeeded | ||||
|     } | ||||
| 
 | ||||
|     fun givenSignoutError(deviceId: String, error: Throwable) { | ||||
|         coEvery { instance.execute(deviceId, any()) } returns Result.failure(error) | ||||
|     } | ||||
| } | ||||
| @ -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<SignoutSessionsUseCase>() | ||||
| 
 | ||||
|     fun givenSignoutSuccess( | ||||
|             deviceIds: List<String>, | ||||
|             interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, | ||||
|     ) { | ||||
|         val interceptor = slot<UserInteractiveAuthInterceptor>() | ||||
|         val flowResponse = mockk<RegistrationFlowResponse>() | ||||
|         val errorCode = "errorCode" | ||||
|         val promise = mockk<Continuation<UIABaseAuth>>() | ||||
|         every { interceptSignoutFlowResponseUseCase.execute(flowResponse, errorCode, promise) } returns SignoutSessionResult.Completed | ||||
|         coEvery { instance.execute(deviceIds, capture(interceptor)) } coAnswers { | ||||
|             secondArg<UserInteractiveAuthInterceptor>().performStage(flowResponse, errorCode, promise) | ||||
|             Result.success(Unit) | ||||
|         } | ||||
|     fun givenSignoutSuccess(deviceIds: List<String>) { | ||||
|         coEvery { instance.execute(deviceIds, any()) } returns Result.success(Unit) | ||||
|     } | ||||
| 
 | ||||
|     fun givenSignoutReAuthNeeded( | ||||
|             deviceIds: List<String>, | ||||
|             interceptSignoutFlowResponseUseCase: InterceptSignoutFlowResponseUseCase, | ||||
|     ): SignoutSessionResult.ReAuthNeeded { | ||||
|         val interceptor = slot<UserInteractiveAuthInterceptor>() | ||||
|     fun givenSignoutReAuthNeeded(deviceIds: List<String>): SignoutSessionsReAuthNeeded { | ||||
|         val flowResponse = mockk<RegistrationFlowResponse>() | ||||
|         every { flowResponse.session } returns "a-session-id" | ||||
|         val errorCode = "errorCode" | ||||
|         val promise = mockk<Continuation<UIABaseAuth>>() | ||||
|         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<UserInteractiveAuthInterceptor>().performStage(flowResponse, errorCode, promise) | ||||
|         coEvery { instance.execute(deviceIds, any()) } coAnswers { | ||||
|             secondArg<(SignoutSessionsReAuthNeeded) -> Unit>().invoke(reAuthNeeded) | ||||
|             Result.success(Unit) | ||||
|         } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user