Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
)

private var pendingMLSGroupCreation: PendingMLSGroupCreation? = null
private var failedMLSGroupCreationId: ConversationId? = null

var newGroupNameTextState: TextFieldState = TextFieldState()
var newGroupState: GroupMetadataState by mutableStateOf(GroupMetadataState())
Expand Down Expand Up @@ -129,6 +130,7 @@
newGroupNameTextState.clearText()
newGroupState = GroupMetadataState()
pendingMLSGroupCreation = null
failedMLSGroupCreationId = null
loadDefaultProtocol()
observeAllowanceOfAppsUsageInitialState()
createGroupState = CreateGroupState.Default
Expand Down Expand Up @@ -205,12 +207,32 @@
createGroupState = CreateGroupState.Default
}

fun discardGroupCreation() {
val conversationId = failedMLSGroupCreationId
if (conversationId == null) {
createGroupState = CreateGroupState.Discarded
return
}

createGroupState = CreateGroupState.Discarding
viewModelScope.launch {
if (createRegularGroup.discardPendingMLSGroupCreation(conversationId)) {
failedMLSGroupCreationId = null
pendingMLSGroupCreation = null
createGroupState = CreateGroupState.Discarded
} else {
createGroupState = CreateGroupState.Error.Unknown

Check warning on line 224 in app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/newconversation/NewConversationViewModel.kt#L224

Added line #L224 was not covered by tests
}
}
}

fun retryPendingMLSGroupCreation() {
val pendingCreation = pendingMLSGroupCreation ?: return
createGroupState = CreateGroupState.Error.PendingMLSCreation(isRetrying = true)
viewModelScope.launch {
when (val result = createRegularGroup.retryPendingMLSGroupCreation(pendingCreation.conversationId)) {
is ConversationCreationResult.Success,
is ConversationCreationResult.BackendConflictFailure,
is ConversationCreationResult.PendingMLSGroupCreation ->
handleNewGroupCreationResult(result, pendingCreation.attempt)

Expand Down Expand Up @@ -376,6 +398,7 @@
return when (result) {
is ConversationCreationResult.Success -> {
pendingMLSGroupCreation = null
failedMLSGroupCreationId = null
newGroupState = newGroupState.copy(isLoading = false)
createGroupState = CreateGroupState.Created(result.conversation.id)
}
Expand Down Expand Up @@ -410,6 +433,8 @@
}

is ConversationCreationResult.BackendConflictFailure -> {
pendingMLSGroupCreation = null
failedMLSGroupCreationId = result.conversationId
groupOptionsState = groupOptionsState.copy(isLoading = false)
newGroupState = newGroupState.copy(isLoading = false)
createGroupState = CreateGroupState.Error.ConflictedBackends(result.domains)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.wire.kalium.logic.data.id.ConversationId
sealed interface CreateGroupState {

data object Default : CreateGroupState
data object Discarding : CreateGroupState
data object Discarded : CreateGroupState

sealed interface Error : CreateGroupState {
data object Unknown : Error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ internal fun GroupOptionRouteScreen(
onNavigateBack: () -> Unit,
) {
LaunchedEffect(newConversationViewModel.createGroupState) {
(newConversationViewModel.createGroupState as? CreateGroupState.Created)?.let {
onConversationCreated(it.conversationId)
when (val state = newConversationViewModel.createGroupState) {
is CreateGroupState.Created -> onConversationCreated(state.conversationId)
CreateGroupState.Discarded -> onDiscard()
else -> Unit
}
}

Expand Down Expand Up @@ -118,8 +120,7 @@ internal fun GroupOptionRouteScreen(
onEditParticipants()
},
onDiscardGroupCreationClick = {
newConversationViewModel.onCreateGroupErrorDismiss()
onDiscard()
newConversationViewModel.discardGroupCreation()
},
onRetryPendingCreation = newConversationViewModel::retryPendingMLSGroupCreation,
onErrorDismissed = newConversationViewModel::onCreateGroupErrorDismiss,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,10 @@ internal fun NewGroupNameRouteScreen(
newConversationViewModel.observeGroupNameChanges()
}
LaunchedEffect(newConversationViewModel.createGroupState) {
(newConversationViewModel.createGroupState as? CreateGroupState.Created)?.let {
onConversationCreated(it.conversationId)
when (val state = newConversationViewModel.createGroupState) {
is CreateGroupState.Created -> onConversationCreated(state.conversationId)
CreateGroupState.Discarded -> onDiscard()
else -> Unit
}
}
GroupNameScreen(
Expand All @@ -70,16 +72,14 @@ internal fun NewGroupNameRouteScreen(
onDismiss = newConversationViewModel::onCreateGroupErrorDismiss,
onRetryPendingCreation = newConversationViewModel::retryPendingMLSGroupCreation,
onPendingCreationAcknowledged = {
newConversationViewModel.onCreateGroupErrorDismiss()
onDiscard()
newConversationViewModel.discardGroupCreation()
},
onEditParticipantsList = {
newConversationViewModel.onCreateGroupErrorDismiss()
onEditParticipants()
},
onCancel = {
newConversationViewModel.onCreateGroupErrorDismiss()
onDiscard()
newConversationViewModel.discardGroupCreation()
},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ internal class NewConversationViewModelArrangement {
// Default empty values
coEvery { isMLSEnabledUseCase() } returns true
coEvery { createRegularGroup(any(), any(), any()) } returns ConversationCreationResult.Success(CONVERSATION)
coEvery { createRegularGroup.discardPendingMLSGroupCreation(any()) } returns true
coEvery { observeChannelsCreationPermissionUseCase() } returns flowOf(ChannelCreationPermission.Forbidden)
coEvery { getDefaultProtocol() } returns SupportedProtocol.PROTEUS
coEvery { isWireCellsEnabled() } returns false
Expand Down Expand Up @@ -189,6 +190,16 @@ internal class NewConversationViewModelArrangement {
)
}

fun withBackendConflictOnCreatingGroup(domains: List<String>) = apply {
coEvery { createRegularGroup(any(), any(), any()) } returns
ConversationCreationResult.BackendConflictFailure(domains)
}

fun withBackendConflictCleanupFallbackOnCreatingGroup(domains: List<String>) = apply {
coEvery { createRegularGroup(any(), any(), any()) } returns
ConversationCreationResult.BackendConflictFailure(domains, CONVERSATION_ID)
}

fun withPendingMLSGroupCreation() = apply {
coEvery { createRegularGroup(any(), any(), any()) } returns ConversationCreationResult.PendingMLSGroupCreation(
CONVERSATION_ID,
Expand All @@ -203,6 +214,11 @@ internal class NewConversationViewModelArrangement {
ConversationCreationResult.UnknownFailure(CoreFailure.Unknown(UnsupportedOperationException("retry failed")))
}

fun withPendingMLSGroupCreationRetryBackendConflict(domains: List<String>) = apply {
coEvery { createRegularGroup.retryPendingMLSGroupCreation(CONVERSATION_ID) } returns
ConversationCreationResult.BackendConflictFailure(domains)
}

fun withConflictingBackendsFailure() = apply {
createGroupState = CreateGroupState.Error.ConflictedBackends(listOf("bella.wire.link", "foma.wire.link"))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,49 @@ class NewConversationViewModelTest {
viewModel.createGroupState shouldBeEqualTo CreateGroupState.Error.Unknown
}

@Test
fun `given backend conflict, when creating group, then should show conflicting backends error`() = runTest {
val domains = listOf("backend-a.example", "backend-b.example")
val (arrangement, viewModel) = NewConversationViewModelArrangement()
.withGetSelfUser(isTeamMember = true)
.withDefaultProtocol(SupportedProtocol.MLS)
.withBackendConflictOnCreatingGroup(domains)
.arrange()

viewModel.createGroup()
advanceUntilIdle()

viewModel.createGroupState shouldBeEqualTo CreateGroupState.Error.ConflictedBackends(domains)

viewModel.discardGroupCreation()
advanceUntilIdle()

viewModel.createGroupState shouldBeEqualTo CreateGroupState.Discarded
coVerify(exactly = 0) {
arrangement.createRegularGroup.discardPendingMLSGroupCreation(NewConversationViewModelArrangement.CONVERSATION_ID)
}
}

@Test
fun `given backend conflict cleanup fallback, when discarding, then should retry cleanup`() = runTest {
val domains = listOf("backend-a.example", "backend-b.example")
val (arrangement, viewModel) = NewConversationViewModelArrangement()
.withGetSelfUser(isTeamMember = true)
.withDefaultProtocol(SupportedProtocol.MLS)
.withBackendConflictCleanupFallbackOnCreatingGroup(domains)
.arrange()

viewModel.createGroup()
advanceUntilIdle()
viewModel.discardGroupCreation()
advanceUntilIdle()

viewModel.createGroupState shouldBeEqualTo CreateGroupState.Discarded
coVerify(exactly = 1) {
arrangement.createRegularGroup.discardPendingMLSGroupCreation(NewConversationViewModelArrangement.CONVERSATION_ID)
}
}

@Test
fun `given MLS establish fails after creation, when retrying, then existing conversation is reused`() = runTest {
val (arrangement, viewModel) = NewConversationViewModelArrangement()
Expand Down Expand Up @@ -130,6 +173,30 @@ class NewConversationViewModelTest {
viewModel.createGroupState shouldBeEqualTo CreateGroupState.Error.PendingMLSCreation()
}

@Test
fun `given pending MLS creation, when retry finds backend conflict, then should show conflicting backends error`() = runTest {
val domains = listOf("backend-a.example", "backend-b.example")
val (arrangement, viewModel) = NewConversationViewModelArrangement()
.withGetSelfUser(isTeamMember = true)
.withDefaultProtocol(SupportedProtocol.MLS)
.withPendingMLSGroupCreation()
.withPendingMLSGroupCreationRetryBackendConflict(domains)
.arrange()
advanceUntilIdle()

viewModel.createGroup()
advanceUntilIdle()
viewModel.retryPendingMLSGroupCreation()
advanceUntilIdle()

viewModel.createGroupState shouldBeEqualTo CreateGroupState.Error.ConflictedBackends(domains)
viewModel.retryPendingMLSGroupCreation()
advanceUntilIdle()
coVerify(exactly = 1) {
arrangement.createRegularGroup.retryPendingMLSGroupCreation(NewConversationViewModelArrangement.CONVERSATION_ID)
}
}

@Test
fun `given no failure, when creating group, then options state should have no error`() = runTest {
val (_, viewModel) = NewConversationViewModelArrangement()
Expand Down
2 changes: 1 addition & 1 deletion kalium
Submodule kalium updated 53 files
+8 −2 core/common/src/commonMain/kotlin/com/wire/kalium/common/error/CoreFailure.kt
+0 −6 core/common/src/commonMain/kotlin/com/wire/kalium/common/error/CoreFailureMappers.kt
+10 −0 core/common/src/commonMain/kotlin/com/wire/kalium/common/error/MLSTransportFailureSerialization.kt
+60 −0 core/common/src/commonTest/kotlin/com/wire/kalium/common/error/MLSTransportFailureSerializationTest.kt
+0 −5 data/network-model/src/commonMain/kotlin/com/wire/kalium/network/api/model/ErrorResponse.kt
+2 −41 data/network/src/commonMain/kotlin/com/wire/kalium/network/api/v13/authenticated/MLSMessageApiV13.kt
+2 −13 data/network/src/commonMain/kotlin/com/wire/kalium/network/utils/HttpResponseHandler.kt
+0 −124 ...ommonTest/kotlin/com/wire/kalium/api/util/FederationErrorResponseInterceptorConflictWithMissingUsersTest.kt
+0 −17 data/network/src/commonTest/kotlin/com/wire/kalium/api/util/WrapRequestTest.kt
+45 −72 data/network/src/commonTest/kotlin/com/wire/kalium/api/v13/MLSMessageApiV13Test.kt
+113 −0 data/network/src/commonTest/kotlin/com/wire/kalium/api/v13/UserDetailsApiV13Test.kt
+18 −0 data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Members.sq
+3 −0 data/persistence/src/commonMain/db_user/com/wire/kalium/persistence/Users.sq
+0 −100 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/cache/FlowCache.kt
+1 −0 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAO.kt
+9 −9 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/UserDAOImpl.kt
+1 −0 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAO.kt
+19 −16 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/conversation/ConversationDAOImpl.kt
+27 −4 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/dao/member/MemberDAO.kt
+1 −24 data/persistence/src/commonMain/kotlin/com/wire/kalium/persistence/db/UserDatabaseBuilder.kt
+0 −142 data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/cache/FlowCacheTest.kt
+26 −0 data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/ConversationDAOTest.kt
+49 −0 data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/MemberDAOTest.kt
+9 −0 data/persistence/src/commonTest/kotlin/com/wire/kalium/persistence/dao/UserDAOTest.kt
+3 −0 gradle.properties
+4 −1 logic/api/jvm/logic.api
+4 −1 logic/api/logic.klib.api
+12 −8 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/call/CallRepository.kt
+10 −3 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepository.kt
+34 −2 logic/src/commonMain/kotlin/com/wire/kalium/logic/data/conversation/ConversationRepository.kt
+5 −6 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/UserSessionScope.kt
+5 −14 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/call/ShouldRemoteMuteChecker.kt
+8 −14 ...nMain/kotlin/com/wire/kalium/logic/feature/call/usecase/CreateAndPersistRecentlyEndedCallMetadataUseCase.kt
+2 −5 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationReadDateUseCase.kt
+5 −1 ...mmonMain/kotlin/com/wire/kalium/logic/feature/conversation/createconversation/ConversationCreationResult.kt
+4 −0 ...ommonMain/kotlin/com/wire/kalium/logic/feature/conversation/createconversation/CreateRegularGroupUseCase.kt
+52 −21 ...commonMain/kotlin/com/wire/kalium/logic/feature/conversation/createconversation/GroupConversationCreator.kt
+25 −9 .../src/commonMain/kotlin/com/wire/kalium/logic/feature/conversation/mls/RecoverPendingMLSGroupJoinsUseCase.kt
+1 −2 logic/src/commonMain/kotlin/com/wire/kalium/logic/feature/message/confirmation/ConfirmationDeliveryHandler.kt
+1 −1 .../src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageFailureHandler.kt
+5 −5 logic/src/commonMain/kotlin/com/wire/kalium/logic/sync/receiver/handler/CallingMessageHandler.kt
+7 −2 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/call/CallRepositoryTest.kt
+29 −0 logic/src/commonTest/kotlin/com/wire/kalium/logic/data/conversation/ConversationGroupRepositoryTest.kt
+10 −20 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/call/ShouldRemoteMuteCheckerTest.kt
+21 −40 ...t/kotlin/com/wire/kalium/logic/feature/call/usecase/CreateAndPersistRecentlyEndedCallMetadataUseCaseTest.kt
+127 −4 logic/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/GroupConversationCreatorTest.kt
+13 −32 ...c/src/commonTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationReadDateUseCaseTest.kt
+30 −1 .../commonTest/kotlin/com/wire/kalium/logic/feature/conversation/mls/RecoverPendingMLSGroupJoinsUseCaseTest.kt
+17 −19 ...src/commonTest/kotlin/com/wire/kalium/logic/feature/message/confirmation/ConfirmationDeliveryHandlerTest.kt
+1 −1 .../commonTest/kotlin/com/wire/kalium/logic/sync/receiver/conversation/message/MLSMessageFailureHandlerTest.kt
+5 −7 logic/src/commonTest/kotlin/com/wire/kalium/logic/sync/receiver/handler/CallingMessageHandlerTest.kt
+2 −6 ...mTest/kotlin/com/wire/kalium/logic/feature/conversation/UpdateConversationReadDateUseCaseIntegrationTest.kt
+0 −13 test/mocks/src/commonMain/kotlin/com/wire/kalium/mocks/responses/ErrorResponseJson.kt
Loading