Skip to content

Commit

Permalink
feat: resolve one-on-on conversation when the connection is accepted (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
typfel committed Sep 19, 2023
1 parent a99fad3 commit cde209a
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,7 @@ class UserSessionScope internal constructor(
conversationRepository,
userRepository,
logout,
oneOnOneResolver,
userId,
clientIdProvider
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,20 @@ import com.wire.kalium.logic.data.event.Event
import com.wire.kalium.logic.data.event.EventLoggingStatus
import com.wire.kalium.logic.data.event.logEventProcessing
import com.wire.kalium.logic.data.logout.LogoutReason
import com.wire.kalium.logic.data.user.Connection
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.UserRepository
import com.wire.kalium.logic.feature.CurrentClientIdProvider
import com.wire.kalium.logic.feature.auth.LogoutUseCase
import com.wire.kalium.logic.feature.conversation.mls.OneOnOneResolver
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.map
import com.wire.kalium.logic.functional.onFailure
import com.wire.kalium.logic.functional.onSuccess
import com.wire.kalium.logic.kaliumLogger
import kotlinx.coroutines.flow.first

internal interface UserEventReceiver : EventReceiver<Event.User>

Expand All @@ -45,6 +50,7 @@ internal class UserEventReceiverImpl internal constructor(
private val conversationRepository: ConversationRepository,
private val userRepository: UserRepository,
private val logout: LogoutUseCase,
private val oneOnOneResolver: OneOnOneResolver,
private val selfUserId: UserId,
private val currentClientIdProvider: CurrentClientIdProvider,
) : UserEventReceiver {
Expand Down Expand Up @@ -79,6 +85,9 @@ internal class UserEventReceiverImpl internal constructor(

private suspend fun handleNewConnection(event: Event.User.NewConnection): Either<CoreFailure, Unit> =
connectionRepository.insertConnectionFromEvent(event)
.flatMap {
resolveOneOnOneConversationUponConnectionAccepted(event.connection)
}
.onSuccess {
kaliumLogger
.logEventProcessing(
Expand All @@ -95,6 +104,15 @@ internal class UserEventReceiverImpl internal constructor(
)
}

private suspend fun resolveOneOnOneConversationUponConnectionAccepted(connection: Connection): Either<CoreFailure, Unit> =
if (connection.status == ConnectionState.ACCEPTED) {
userRepository.getKnownUser(connection.qualifiedToId).first()?.let {
oneOnOneResolver.resolveOneOnOneConversationWithUser(it).map { }
} ?: Either.Right(Unit)
} else {
Either.Right(Unit)
}

private suspend fun handleClientRemove(event: Event.User.ClientRemove): Either<CoreFailure, Unit> =
currentClientIdProvider().map { currentClientId ->
if (currentClientId == event.clientId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ object TestEvent {
false, eventId, TestClient.CLIENT
)

fun newConnection(eventId: String = "eventId") = Event.User.NewConnection(
fun newConnection(eventId: String = "eventId", status: ConnectionState = ConnectionState.PENDING) = Event.User.NewConnection(
false,
eventId,
Connection(
Expand All @@ -94,7 +94,7 @@ object TestEvent {
lastUpdate = "lastUpdate",
qualifiedConversationId = TestConversation.ID,
qualifiedToId = TestUser.USER_ID,
status = ConnectionState.PENDING,
status = status,
toId = "told?"
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ import com.wire.kalium.logic.data.conversation.ClientId
import com.wire.kalium.logic.data.conversation.Conversation
import com.wire.kalium.logic.data.conversation.ConversationRepository
import com.wire.kalium.logic.data.logout.LogoutReason
import com.wire.kalium.logic.data.user.ConnectionState
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.user.UserRepository
import com.wire.kalium.logic.feature.CurrentClientIdProvider
import com.wire.kalium.logic.feature.auth.LogoutUseCase
import com.wire.kalium.logic.framework.TestConversation
import com.wire.kalium.logic.framework.TestEvent
import com.wire.kalium.logic.framework.TestUser
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.util.arrangement.UserRepositoryArrangement
import com.wire.kalium.logic.util.arrangement.UserRepositoryArrangementImpl
import com.wire.kalium.logic.util.arrangement.mls.OneOnOneResolverArrangement
import com.wire.kalium.logic.util.arrangement.mls.OneOnOneResolverArrangementImpl
import io.mockative.Mock
import io.mockative.any
import io.mockative.classOf
Expand All @@ -39,6 +44,7 @@ import io.mockative.given
import io.mockative.mock
import io.mockative.once
import io.mockative.verify
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import kotlin.test.Test

Expand All @@ -47,10 +53,10 @@ class UserEventReceiverTest {
@Test
fun givenRemoveClientEvent_whenTheClientIdIsEqualCurrentClient_SoftLogoutInvoked() = runTest {
val event = TestEvent.clientRemove(EVENT_ID, CLIENT_ID1)
val (arrangement, eventReceiver) = Arrangement()
.withCurrentClientIdIs(CLIENT_ID1)
.withLogoutUseCaseSucceed()
.arrange()
val (arrangement, eventReceiver) = arrange {
withCurrentClientIdIs(CLIENT_ID1)
withLogoutUseCaseSucceed()
}

eventReceiver.onEvent(event)

Expand All @@ -63,10 +69,10 @@ class UserEventReceiverTest {
@Test
fun givenRemoveClientEvent_whenTheClientIdIsNotEqualCurrentClient_SoftLogoutNotInvoked() = runTest {
val event = TestEvent.clientRemove(EVENT_ID, CLIENT_ID1)
val (arrangement, eventReceiver) = Arrangement()
.withCurrentClientIdIs(CLIENT_ID2)
.withLogoutUseCaseSucceed()
.arrange()
val (arrangement, eventReceiver) = arrange {
withCurrentClientIdIs(CLIENT_ID2)
withLogoutUseCaseSucceed()
}

eventReceiver.onEvent(event)

Expand All @@ -79,9 +85,9 @@ class UserEventReceiverTest {
@Test
fun givenDeleteAccountEvent_SoftLogoutInvoked() = runTest {
val event = TestEvent.userDelete(userId = SELF_USER_ID)
val (arrangement, eventReceiver) = Arrangement()
.withLogoutUseCaseSucceed()
.arrange()
val (arrangement, eventReceiver) = arrange {
withLogoutUseCaseSucceed()
}

eventReceiver.onEvent(event)

Expand All @@ -94,10 +100,11 @@ class UserEventReceiverTest {
@Test
fun givenUserDeleteEvent_RepoAndPersisMessageAreInvoked() = runTest {
val event = TestEvent.userDelete(userId = OTHER_USER_ID)
val (arrangement, eventReceiver) = Arrangement()
.withUserDeleteSuccess()
.withConversationsByUserId(listOf(TestConversation.CONVERSATION))
.arrange()
val (arrangement, eventReceiver) = arrange {
withRemoveUserSuccess()
withDeleteUserFromConversationsSuccess()
withConversationsByUserId(listOf(TestConversation.CONVERSATION))
}

eventReceiver.onEvent(event)

Expand All @@ -115,9 +122,9 @@ class UserEventReceiverTest {
@Test
fun givenUserUpdateEvent_RepoIsInvoked() = runTest {
val event = TestEvent.updateUser(userId = SELF_USER_ID)
val (arrangement, eventReceiver) = Arrangement()
.withUpdateUserSuccess()
.arrange()
val (arrangement, eventReceiver) = arrange {
withUpdateUserSuccess()
}

eventReceiver.onEvent(event)

Expand All @@ -130,8 +137,7 @@ class UserEventReceiverTest {
@Test
fun givenNewClientEvent_NewClientManagerInvoked() = runTest {
val event = TestEvent.newClient()
val (arrangement, eventReceiver) = Arrangement()
.arrange()
val (arrangement, eventReceiver) = arrange { }

eventReceiver.onEvent(event)

Expand All @@ -141,16 +147,63 @@ class UserEventReceiverTest {
.wasInvoked(exactly = once)
}

private class Arrangement {
@Test
fun givenNewConnectionEvent_thenConnectionIsPersisted() = runTest {
val event = TestEvent.newConnection(status = ConnectionState.PENDING)
val (arrangement, eventReceiver) = arrange {
withInsertConnectionFromEventSucceeding()
}

eventReceiver.onEvent(event)

verify(arrangement.connectionRepository)
.suspendFunction(arrangement.connectionRepository::insertConnectionFromEvent)
.with(any())
.wasInvoked(exactly = once)
}

@Test
fun givenNewConnectionEventWithStatusPending_thenActiveOneOnOneConversationIsNotResolved() = runTest {
val event = TestEvent.newConnection(status = ConnectionState.PENDING).copy()
val (arrangement, eventReceiver) = arrange {
withInsertConnectionFromEventSucceeding()
}

eventReceiver.onEvent(event)

verify(arrangement.oneOnOneResolver)
.suspendFunction(arrangement.oneOnOneResolver::resolveOneOnOneConversationWithUser)
.with(any())
.wasNotInvoked()
}

@Test
fun givenNewConnectionEventWithStatusAccepted_thenActiveOneOnOneConversationIsResolved() = runTest {
val event = TestEvent.newConnection(status = ConnectionState.ACCEPTED).copy()
val (arrangement, eventReceiver) = arrange {
withInsertConnectionFromEventSucceeding()
withGetKnownUserReturning(flowOf(TestUser.OTHER))
withResolveOneOnOneConversationWithUserReturning(Either.Right(TestConversation.ID))
}

eventReceiver.onEvent(event)

verify(arrangement.oneOnOneResolver)
.suspendFunction(arrangement.oneOnOneResolver::resolveOneOnOneConversationWithUser)
.with(eq(TestUser.OTHER))
.wasInvoked(exactly = once)
}

private class Arrangement(private val block: Arrangement.() -> Unit) :
UserRepositoryArrangement by UserRepositoryArrangementImpl(),
OneOnOneResolverArrangement by OneOnOneResolverArrangementImpl()
{
@Mock
val connectionRepository = mock(classOf<ConnectionRepository>())

@Mock
val logoutUseCase = mock(classOf<LogoutUseCase>())

@Mock
val userRepository = mock(classOf<UserRepository>())

@Mock
val conversationRepository = mock(classOf<ConversationRepository>())

Expand All @@ -166,6 +219,7 @@ class UserEventReceiverTest {
conversationRepository,
userRepository,
logoutUseCase,
oneOnOneResolver,
SELF_USER_ID,
currentClientIdProvider
)
Expand All @@ -174,6 +228,13 @@ class UserEventReceiverTest {
withSaveNewClientSucceeding()
}

fun withInsertConnectionFromEventSucceeding() = apply {
given(connectionRepository)
.suspendFunction(connectionRepository::insertConnectionFromEvent)
.whenInvokedWith(any())
.thenReturn(Either.Right(Unit))
}

fun withSaveNewClientSucceeding() = apply {
given(clientRepository)
.suspendFunction(clientRepository::saveNewClientEvent)
Expand All @@ -192,14 +253,7 @@ class UserEventReceiverTest {
given(logoutUseCase).suspendFunction(logoutUseCase::invoke).whenInvokedWith(any()).thenReturn(Unit)
}

fun withUpdateUserSuccess() = apply {
given(userRepository).suspendFunction(userRepository::updateUserFromEvent).whenInvokedWith(any())
.thenReturn(Either.Right(Unit))
}

fun withUserDeleteSuccess() = apply {
given(userRepository).suspendFunction(userRepository::removeUser)
.whenInvokedWith(any()).thenReturn(Either.Right(Unit))
fun withDeleteUserFromConversationsSuccess() = apply {
given(conversationRepository).suspendFunction(conversationRepository::deleteUserFromConversations)
.whenInvokedWith(any()).thenReturn(Either.Right(Unit))
}
Expand All @@ -209,10 +263,15 @@ class UserEventReceiverTest {
.whenInvokedWith(any()).thenReturn(Either.Right(conversationIds))
}

fun arrange() = this to userEventReceiver
fun arrange() = run {
block()
this@Arrangement to userEventReceiver
}
}

companion object {
private fun arrange(configuration: Arrangement.() -> Unit) = Arrangement(configuration).arrange()

const val EVENT_ID = "1234"
val SELF_USER_ID = UserId("alice", "wonderland")
val OTHER_USER_ID = UserId("john", "public")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ import kotlinx.coroutines.flow.Flow
internal interface UserRepositoryArrangement {
val userRepository: UserRepository

fun withUpdateUserSuccess()

fun withRemoveUserSuccess()

fun withSelfUserReturning(selfUser: SelfUser?)

fun withUserByIdReturning(result: Either<CoreFailure, OtherUser>)
Expand All @@ -47,6 +51,16 @@ internal class UserRepositoryArrangementImpl: UserRepositoryArrangement {
@Mock
override val userRepository: UserRepository = mock(UserRepository::class)

override fun withUpdateUserSuccess() {
given(userRepository).suspendFunction(userRepository::updateUserFromEvent).whenInvokedWith(any())
.thenReturn(Either.Right(Unit))
}

override fun withRemoveUserSuccess() {
given(userRepository).suspendFunction(userRepository::removeUser)
.whenInvokedWith(any()).thenReturn(Either.Right(Unit))
}

override fun withSelfUserReturning(selfUser: SelfUser?) {
given(userRepository)
.suspendFunction(userRepository::getSelfUser)
Expand Down

0 comments on commit cde209a

Please sign in to comment.