Skip to content

Commit

Permalink
feat: use case to send button action confirmations (WPB-2633) (#2393)
Browse files Browse the repository at this point in the history
* feat: use case to send button action confirmations (WPB-2633)

* Fixed issue with domain of button sender
  • Loading branch information
mythsunwind authored Jan 23, 2024
1 parent cd403f1 commit ee72866
Show file tree
Hide file tree
Showing 8 changed files with 353 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.wire.kalium.protobuf.encodeToByteArray
import com.wire.kalium.protobuf.messages.Asset
import com.wire.kalium.protobuf.messages.Button
import com.wire.kalium.protobuf.messages.ButtonAction
import com.wire.kalium.protobuf.messages.ButtonActionConfirmation
import com.wire.kalium.protobuf.messages.Calling
import com.wire.kalium.protobuf.messages.Cleared
import com.wire.kalium.protobuf.messages.ClientAction
Expand Down Expand Up @@ -132,7 +133,7 @@ class ProtoContentMapperImpl(
is MessageContent.Composite -> packComposite(readableContent, expectsReadConfirmation, legalHoldStatus)
is MessageContent.ButtonAction -> packButtonAction(readableContent)

is MessageContent.ButtonActionConfirmation -> TODO()
is MessageContent.ButtonActionConfirmation -> packButtonActionConfirmation(readableContent)
is MessageContent.Location -> packLocation(readableContent, expectsReadConfirmation, legalHoldStatus)
}
}
Expand Down Expand Up @@ -165,6 +166,16 @@ class ProtoContentMapperImpl(
)
)

private fun packButtonActionConfirmation(
readableContent: MessageContent.ButtonActionConfirmation
): GenericMessage.Content.ButtonActionConfirmation =
GenericMessage.Content.ButtonActionConfirmation(
ButtonActionConfirmation(
buttonId = readableContent.buttonId,
referenceMessageId = readableContent.referencedMessageId
)
)

private fun packComposite(
readableContent: MessageContent.Composite,
expectsReadConfirmation: Boolean,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCa
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageDownloadStatusUseCaseImpl
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageUploadStatusUseCase
import com.wire.kalium.logic.feature.asset.UpdateAssetMessageUploadStatusUseCaseImpl
import com.wire.kalium.logic.feature.message.composite.SendButtonActionConfirmationMessageUseCase
import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase
import com.wire.kalium.logic.feature.message.composite.SendButtonMessageUseCase
import com.wire.kalium.logic.feature.message.ephemeral.DeleteEphemeralMessageForSelfUserAsReceiverUseCaseImpl
Expand Down Expand Up @@ -345,6 +346,14 @@ class MessageScope internal constructor(
val resetSession: ResetSessionUseCase
get() = ResetSessionUseCaseImpl(proteusClientProvider, sessionResetSender, messageRepository)

val sendButtonActionConfirmationMessage: SendButtonActionConfirmationMessageUseCase
get() = SendButtonActionConfirmationMessageUseCase(
syncManager = syncManager,
messageSender = messageSender,
selfUserId = selfUserId,
currentClientIdProvider = currentClientIdProvider
)

val sendButtonActionMessage: SendButtonActionMessageUseCase
get() = SendButtonActionMessageUseCase(
syncManager = syncManager,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.message.composite

import com.benasher44.uuid.uuid4
import com.wire.kalium.logic.CoreFailure
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.Message
import com.wire.kalium.logic.data.message.MessageContent
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.data.id.CurrentClientIdProvider
import com.wire.kalium.logic.feature.message.MessageSender
import com.wire.kalium.logic.data.message.MessageTarget
import com.wire.kalium.logic.functional.flatMap
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.sync.SyncManager
import com.wire.kalium.util.DateTimeUtil

/**
* Use case for sending a button action message.
* @param conversationId The conversation id.
* @param messageId The id of the message that contains the button.
* @param buttonId The id of the button.
*
* the action message is sent only to the message original sender.
*/
class SendButtonActionConfirmationMessageUseCase internal constructor(
private val messageSender: MessageSender,
private val syncManager: SyncManager,
private val currentClientIdProvider: CurrentClientIdProvider,
private val selfUserId: UserId
) {
suspend operator fun invoke(
conversationId: ConversationId,
messageId: String,
buttonId: String,
userIds: List<UserId>
): Result = syncManager.waitUntilLiveOrFailure().flatMap {
currentClientIdProvider().flatMap { currentClientId ->
val regularMessage = Message.Signaling(
id = uuid4().toString(),
content = MessageContent.ButtonActionConfirmation(
referencedMessageId = messageId,
buttonId = buttonId
),
conversationId = conversationId,
date = DateTimeUtil.currentIsoDateTimeString(),
senderUserId = selfUserId,
senderClientId = currentClientId,
status = Message.Status.Pending,
isSelfMessage = true,
expirationData = null
)
messageSender.sendMessage(regularMessage, messageTarget = MessageTarget.Users(userIds))
}
}.fold(Result::Failure, { Result.Success })

sealed interface Result {
data object Success : Result
data class Failure(
val error: CoreFailure
) : Result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.kalium.logic.feature.message.composite

import com.wire.kalium.logic.data.conversation.ClientId
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.message.MessageTarget
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.functional.Either
import com.wire.kalium.logic.util.arrangement.MessageSenderArrangement
import com.wire.kalium.logic.util.arrangement.MessageSenderArrangementImpl
import com.wire.kalium.logic.util.arrangement.SyncManagerArrangement
import com.wire.kalium.logic.util.arrangement.SyncManagerArrangementImpl
import com.wire.kalium.logic.util.arrangement.provider.CurrentClientIdProviderArrangement
import com.wire.kalium.logic.util.arrangement.provider.CurrentClientIdProviderArrangementImpl
import io.mockative.any
import io.mockative.matching
import io.mockative.once
import io.mockative.verify
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertIs

class SendButtonActionConfirmationMessageTest {

@Test
fun givenMessageSendingSuccess_thenMessageIsSentOnlyToOriginalSenderOfTheButtonAction() = runTest {
val convId = ConversationId("conversation-id", "conversation-domain")
val buttonActionSender = UserId("action-sender-id", "action-sender-domain")
val (arrangement, useCase) = Arrangement()
.arrange {
withWaitUntilLiveOrFailure(Either.Right(Unit))
withCurrentClientIdSuccess(ClientId("client-id"))
withSendMessageSucceed()
}

val result = useCase(
conversationId = convId,
messageId = "message-id",
buttonId = "button-id",
userIds = listOf(buttonActionSender)
)

assertIs<SendButtonActionConfirmationMessageUseCase.Result.Success>(result)

verify(arrangement.messageSender)
.suspendFunction(arrangement.messageSender::sendMessage)
.with(any(), matching {
it is MessageTarget.Users && it.userId == listOf(buttonActionSender)
})
.wasInvoked(exactly = once)

verify(arrangement.currentClientIdProvider)
.suspendFunction(arrangement.currentClientIdProvider::invoke)
.wasInvoked(exactly = once)

verify(arrangement.syncManager)
.suspendFunction(arrangement.syncManager::waitUntilLiveOrFailure)
.wasInvoked(exactly = once)
}

private companion object {
val SELF_USER_ID: UserId = UserId("self-user-id", "self-user-domain")
}

private class Arrangement :
MessageSenderArrangement by MessageSenderArrangementImpl(),
SyncManagerArrangement by SyncManagerArrangementImpl(),
CurrentClientIdProviderArrangement by CurrentClientIdProviderArrangementImpl() {

private lateinit var useCase: SendButtonActionConfirmationMessageUseCase

fun arrange(block: Arrangement.() -> Unit): Pair<Arrangement, SendButtonActionConfirmationMessageUseCase> {
apply(block)
useCase = SendButtonActionConfirmationMessageUseCase(
messageSender = messageSender,
syncManager = syncManager,
currentClientIdProvider = currentClientIdProvider,
selfUserId = SELF_USER_ID,
)

return this to useCase
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import com.wire.kalium.testservice.managed.InstanceService
import com.wire.kalium.testservice.models.ClearConversationRequest
import com.wire.kalium.testservice.models.DeleteMessageRequest
import com.wire.kalium.testservice.models.GetMessagesRequest
import com.wire.kalium.testservice.models.SendButtonActionConfirmationRequest
import com.wire.kalium.testservice.models.SendButtonActionRequest
import com.wire.kalium.testservice.models.SendConfirmationReadRequest
import com.wire.kalium.testservice.models.SendEphemeralConfirmationDeliveredRequest
import com.wire.kalium.testservice.models.SendFileRequest
Expand Down Expand Up @@ -297,9 +299,42 @@ class ConversationResources(private val instanceService: InstanceService) {

// POST /api/v1/instance/{instanceId}/sendButtonAction
// Send a button action to a poll.
@POST
@Path("/instance/{id}/sendButtonAction")
@Operation(summary = "Send a button action to a poll.")
@Consumes(MediaType.APPLICATION_JSON)
fun sendButtonActionConfirmation(@PathParam("id") id: String, @Valid request: SendButtonActionRequest): Response {
val instance = instanceService.getInstanceOrThrow(id)
return with(request) {
runBlocking {
ConversationRepository.sendButtonAction(
instance,
ConversationId(conversationId, conversationDomain),
referenceMessageId,
buttonId
)
}
}
}

// POST /api/v1/instance/{instanceId}/sendButtonActionConfirmation
// Send a confirmation to a button action.
@POST
@Path("/instance/{id}/sendButtonActionConfirmation")
@Operation(summary = "Send a confirmation to a button action.")
@Consumes(MediaType.APPLICATION_JSON)
fun sendButtonActionConfirmation(@PathParam("id") id: String, @Valid request: SendButtonActionConfirmationRequest): Response {
val instance = instanceService.getInstanceOrThrow(id)
return with(request) {
runBlocking {
ConversationRepository.sendButtonActionConfirmation(
instance,
ConversationId(conversationId, conversationDomain),
referenceMessageId,
buttonId,
userIds.map { UserId(it, conversationDomain) }
)
}
}
}

@POST
@Path("/instance/{id}/sendReaction")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import com.wire.kalium.logic.feature.conversation.ClearConversationContentUseCas
import com.wire.kalium.logic.feature.debug.BrokenState
import com.wire.kalium.logic.feature.debug.SendBrokenAssetMessageResult
import com.wire.kalium.logic.data.message.SelfDeletionTimer
import com.wire.kalium.logic.data.user.UserId
import com.wire.kalium.logic.feature.message.composite.SendButtonActionConfirmationMessageUseCase
import com.wire.kalium.logic.feature.message.composite.SendButtonActionMessageUseCase
import com.wire.kalium.logic.feature.session.CurrentSessionResult
import com.wire.kalium.logic.functional.fold
import com.wire.kalium.logic.functional.onFailure
Expand All @@ -48,6 +51,7 @@ import kotlin.time.toDuration

sealed class ConversationRepository {

@Suppress("TooManyFunctions")
companion object {
private val log = LoggerFactory.getLogger(ConversationRepository::class.java.name)

Expand Down Expand Up @@ -120,6 +124,67 @@ sealed class ConversationRepository {
}
}

suspend fun sendButtonAction(
instance: Instance,
conversationId: ConversationId,
referenceMessageId: String,
buttonId: String
): Response = instance.coreLogic.globalScope {
when (val session = session.currentSession()) {
is CurrentSessionResult.Success -> {
instance.coreLogic.sessionScope(session.accountInfo.userId) {
log.info("Instance ${instance.instanceId}: Send button action for button $buttonId")
when (val result = messages.sendButtonActionMessage(conversationId, referenceMessageId, buttonId)) {
is SendButtonActionMessageUseCase.Result.Failure ->
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity(result).build()

else -> {
Response.status(Response.Status.OK)
.entity(SendTextResponse(instance.instanceId, "", "")).build()
}
}
}
}

is CurrentSessionResult.Failure -> {
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Session failure").build()
}
}
}

suspend fun sendButtonActionConfirmation(
instance: Instance,
conversationId: ConversationId,
referenceMessageId: String,
buttonId: String,
userIds: List<UserId>
): Response = instance.coreLogic.globalScope {
when (val session = session.currentSession()) {
is CurrentSessionResult.Success -> {
instance.coreLogic.sessionScope(session.accountInfo.userId) {
log.info("Instance ${instance.instanceId}: Send button action confirmation for button $buttonId")
when (val result = messages.sendButtonActionConfirmationMessage(
conversationId,
referenceMessageId,
buttonId,
userIds
)) {
is SendButtonActionConfirmationMessageUseCase.Result.Failure -> Response
.status(Response.Status.INTERNAL_SERVER_ERROR).entity(result).build()

else -> {
Response.status(Response.Status.OK).entity(SendTextResponse(instance.instanceId, "", "")).build()
}
}
}
}

is CurrentSessionResult.Failure -> {
Response.status(Response.Status.INTERNAL_SERVER_ERROR).entity("Session failure").build()
}
}
}

suspend fun sendReaction(
instance: Instance,
conversationId: ConversationId,
Expand Down
Loading

0 comments on commit ee72866

Please sign in to comment.