|
18 | 18 |
|
19 | 19 | package com.wire.kalium.logic.feature.conversation |
20 | 20 |
|
| 21 | +import com.wire.kalium.common.error.MLSFailure |
21 | 22 | import com.wire.kalium.common.error.StorageFailure |
| 23 | +import com.wire.kalium.logic.data.conversation.Conversation.ProtocolInfo.MLSCapable.GroupState |
22 | 24 | import com.wire.kalium.logic.data.client.ClientRepository |
23 | 25 | import com.wire.kalium.logic.data.conversation.Conversation |
24 | 26 | import com.wire.kalium.logic.data.conversation.ConversationRepository |
@@ -143,6 +145,72 @@ class RecoverMLSConversationsUseCaseTests { |
143 | 145 | assertIs<RecoverMLSConversationsResult.Success>(actual) |
144 | 146 | } |
145 | 147 |
|
| 148 | + @Test |
| 149 | + fun givenEpochCheckFailsWithConversationNotFound_ThenGroupIsMarkedPendingAndOtherGroupsProcessed() = runTest { |
| 150 | + val conversations = listOf(Arrangement.MLS_CONVERSATION1, Arrangement.MLS_CONVERSATION2) |
| 151 | + val (arrangement, recoverMLSConversationsUseCase) = Arrangement() |
| 152 | + .withConversationsByGroupStateReturns(Either.Right(conversations)) |
| 153 | + .withJoinExistingMLSConversationUseCaseSuccessful() |
| 154 | + .withIsMLSSupported(true) |
| 155 | + .withHasRegisteredMLSClient(true) |
| 156 | + .withUpdateConversationGroupStateSuccessful() |
| 157 | + .withEpochCheckFailsWithConversationNotFoundFor(Arrangement.GROUP_ID1) |
| 158 | + .withConversationIsOutOfSyncReturnsTrueFor(listOf(Arrangement.GROUP_ID2)) |
| 159 | + .arrange() |
| 160 | + |
| 161 | + val actual = recoverMLSConversationsUseCase(arrangement.transactionContext) |
| 162 | + |
| 163 | + // Both groups should have epoch check attempted |
| 164 | + coVerify { |
| 165 | + arrangement.mlsConversationRepository.isLocalGroupEpochStale(any(), any(), any()) |
| 166 | + }.wasInvoked(conversations.size) |
| 167 | + |
| 168 | + // First group should be marked as PENDING_AFTER_RESET so it can be re-joined on next sync |
| 169 | + coVerify { |
| 170 | + arrangement.conversationRepository.updateConversationGroupState( |
| 171 | + eq(Arrangement.GROUP_ID1), eq(GroupState.PENDING_AFTER_RESET) |
| 172 | + ) |
| 173 | + }.wasInvoked(once) |
| 174 | + |
| 175 | + // Only the second group should be joined (first was marked pending due to ConversationNotFound) |
| 176 | + coVerify { |
| 177 | + arrangement.joinExistingMLSConversationUseCase.invoke(any(), eq(Arrangement.MLS_CONVERSATION2.id), any()) |
| 178 | + }.wasInvoked(once) |
| 179 | + |
| 180 | + // Overall result should be Success since ConversationNotFound is non-fatal |
| 181 | + assertIs<RecoverMLSConversationsResult.Success>(actual) |
| 182 | + } |
| 183 | + |
| 184 | + @Test |
| 185 | + fun givenAllGroupsFailWithConversationNotFound_ThenAllMarkedPendingAndResultIsSuccess() = runTest { |
| 186 | + val conversations = listOf(Arrangement.MLS_CONVERSATION1, Arrangement.MLS_CONVERSATION2) |
| 187 | + val (arrangement, recoverMLSConversationsUseCase) = Arrangement() |
| 188 | + .withConversationsByGroupStateReturns(Either.Right(conversations)) |
| 189 | + .withJoinExistingMLSConversationUseCaseSuccessful() |
| 190 | + .withIsMLSSupported(true) |
| 191 | + .withHasRegisteredMLSClient(true) |
| 192 | + .withUpdateConversationGroupStateSuccessful() |
| 193 | + .withEpochCheckFailsWithConversationNotFoundFor(Arrangement.GROUP_ID1) |
| 194 | + .withEpochCheckFailsWithConversationNotFoundFor(Arrangement.GROUP_ID2) |
| 195 | + .arrange() |
| 196 | + |
| 197 | + val actual = recoverMLSConversationsUseCase(arrangement.transactionContext) |
| 198 | + |
| 199 | + // Both groups should be marked as PENDING_AFTER_RESET |
| 200 | + coVerify { |
| 201 | + arrangement.conversationRepository.updateConversationGroupState( |
| 202 | + any(), eq(GroupState.PENDING_AFTER_RESET) |
| 203 | + ) |
| 204 | + }.wasInvoked(conversations.size) |
| 205 | + |
| 206 | + // No groups should be joined since all were marked pending |
| 207 | + coVerify { |
| 208 | + arrangement.joinExistingMLSConversationUseCase.invoke(any(), any(), any()) |
| 209 | + }.wasNotInvoked() |
| 210 | + |
| 211 | + assertIs<RecoverMLSConversationsResult.Success>(actual) |
| 212 | + } |
| 213 | + |
146 | 214 | @Test |
147 | 215 | fun whenFetchingListOfConversationsFails_ThenShouldReturnFailure() = runTest { |
148 | 216 | val (arrangement, recoverMLSConversationsUseCase) = Arrangement() |
@@ -216,6 +284,18 @@ class RecoverMLSConversationsUseCaseTests { |
216 | 284 | }.returns(Either.Right(true)) |
217 | 285 | } |
218 | 286 |
|
| 287 | + suspend fun withUpdateConversationGroupStateSuccessful() = apply { |
| 288 | + coEvery { |
| 289 | + conversationRepository.updateConversationGroupState(any(), any()) |
| 290 | + }.returns(Either.Right(Unit)) |
| 291 | + } |
| 292 | + |
| 293 | + suspend fun withEpochCheckFailsWithConversationNotFoundFor(groupID: GroupID) = apply { |
| 294 | + coEvery { |
| 295 | + mlsConversationRepository.isLocalGroupEpochStale(any(), eq(groupID), any()) |
| 296 | + }.returns(Either.Left(MLSFailure.ConversationNotFound)) |
| 297 | + } |
| 298 | + |
219 | 299 | suspend fun withJoinExistingMLSConversationUseCaseFailsFor(failedGroupId: ConversationId) = apply { |
220 | 300 | coEvery { |
221 | 301 | joinExistingMLSConversationUseCase.invoke(any(), eq(failedGroupId), any()) |
|
0 commit comments