status-im/status-go

View on GitHub
protocol/messenger_remove_message_test.go

Summary

Maintainability
A
0 mins
Test Coverage
package protocol

import (
    "context"
    "testing"

    "github.com/stretchr/testify/suite"

    "github.com/status-im/status-go/protocol/common"
    "github.com/status-im/status-go/protocol/protobuf"
    "github.com/status-im/status-go/server"
)

func TestMessengerRemoveMessageSuite(t *testing.T) {
    suite.Run(t, new(MessengerRemoveMessageSuite))
}

type MessengerRemoveMessageSuite struct {
    MessengerBaseTestSuite
}

func (s *MessengerRemoveMessageSuite) TestDeleteMessage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage := buildTestMessage(*theirChat)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    messageID := sendResponse.Messages()[0].ID

    response, err := WaitOnMessengerResponse(
        s.m,
        func(r *MessengerResponse) bool { return len(r.messages) > 0 },
        "no messages",
    )
    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 1)

    ogMessage := sendResponse.Messages()[0]

    sendResponse, err = theirMessenger.DeleteMessageAndSend(context.Background(), ogMessage.ID)

    s.Require().NoError(err)
    s.Require().Len(sendResponse.Messages(), 0)
    s.Require().Len(sendResponse.RemovedMessages(), 1)
    s.Require().Equal(messageID, sendResponse.RemovedMessages()[0].MessageID)
    s.Require().Equal(sendResponse.RemovedMessages()[0].DeletedBy, "")
    s.Require().Len(sendResponse.Chats(), 1)
    // LastMessage is marked as deleted
    lastRemovedMessage := sendResponse.Chats()[0].LastMessage
    s.Require().Equal(lastRemovedMessage.ID, messageID)
    s.Require().Equal(lastRemovedMessage.Deleted, true)

    // Main instance user attempts to delete the message it received from theirMessenger
    _, err = s.m.DeleteMessageAndSend(context.Background(), ogMessage.ID)

    s.Require().ErrorContains(err, "can't find chat")
}

func (s *MessengerRemoveMessageSuite) TestDeleteMessagePreviousLastMessage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage1 := buildTestMessage(*theirChat)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage1)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    inputMessage2 := buildTestMessage(*theirChat)
    sendResponse, err = theirMessenger.SendChatMessage(context.Background(), inputMessage2)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    messageID := sendResponse.Messages()[0].ID

    response, err := WaitOnMessengerResponse(
        s.m,
        func(r *MessengerResponse) bool { return len(r.messages) > 0 },
        "no messages",
    )
    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 2)

    ogMessage := sendResponse.Messages()[0]

    sendResponse, err = theirMessenger.DeleteMessageAndSend(context.Background(), ogMessage.ID)

    s.Require().NoError(err)
    s.Require().Len(sendResponse.Messages(), 0)
    s.Require().Len(sendResponse.RemovedMessages(), 1)
    s.Require().Equal(messageID, sendResponse.RemovedMessages()[0].MessageID)
    s.Require().Len(sendResponse.Chats(), 1)
    // LastMessage is updated and marked as deleted
    s.Require().NotNil(sendResponse.Chats()[0].LastMessage)
    s.Require().Equal(inputMessage2.ID, sendResponse.Chats()[0].LastMessage.ID)
    s.Require().Equal(sendResponse.Chats()[0].LastMessage.Deleted, true)

}

func (s *MessengerRemoveMessageSuite) TestDeleteWrongMessageType() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage := buildTestGapMessage(*theirChat)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    ogMessage := sendResponse.Messages()[0]

    // Delete should not work
    _, err = theirMessenger.DeleteMessageAndSend(context.Background(), ogMessage.ID)

    s.Require().Equal(ErrInvalidDeleteTypeAuthor, err)
}

// TODO fix activity center notifications not being deleted when a message is deleted

func (s *MessengerRemoveMessageSuite) TestDeleteMessageFirstThenMessage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    contact, err := BuildContactFromPublicKey(&theirMessenger.identity.PublicKey)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)
    messageID := "message-id"

    inputMessage := buildTestMessage(*theirChat)
    inputMessage.Clock = 1
    deleteMessage := &DeleteMessage{
        DeleteMessage: &protobuf.DeleteMessage{
            Clock:       2,
            MessageType: protobuf.MessageType_ONE_TO_ONE,
            MessageId:   messageID,
            ChatId:      theirChat.ID,
        },
        From: common.PubkeyToHex(&theirMessenger.identity.PublicKey),
    }

    state := &ReceivedMessageState{
        Response: &MessengerResponse{},
    }

    // Handle Delete first
    err = s.m.handleDeleteMessage(state, deleteMessage)
    s.Require().NoError(err)

    // // Handle chat message
    state = &ReceivedMessageState{
        Response: &MessengerResponse{},
        CurrentMessageState: &CurrentMessageState{
            MessageID:        messageID,
            WhisperTimestamp: s.m.getTimesource().GetCurrentTime(),
            Contact:          contact,
            PublicKey:        &theirMessenger.identity.PublicKey,
        },
    }
    err = s.m.HandleChatMessage(state, inputMessage.ChatMessage, nil, false)
    s.Require().NoError(err)
    s.Require().Len(state.Response.Messages(), 0) // Message should not be added to response
    s.Require().Len(state.Response.RemovedMessages(), 0)
    s.Require().Nil(state.Response.Chats()[0].LastMessage)
}

func (s *MessengerRemoveMessageSuite) TestDeleteImageMessage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    messageCount := 3
    var album []*common.Message
    for i := 0; i < messageCount; i++ {
        image, err := buildImageWithoutAlbumIDMessage(*ourChat)
        s.NoError(err)
        album = append(album, image)
    }

    response, err := s.m.SendChatMessages(context.Background(), album)
    s.NoError(err)

    // Check that album count was the number of the images sent
    imagesCount := uint32(0)
    for _, message := range response.Messages() {
        if message.ContentType == protobuf.ChatMessage_IMAGE {
            imagesCount++
        }
    }
    for _, message := range response.Messages() {
        s.Require().NotNil(message.GetImage())
        s.Require().Equal(message.GetImage().AlbumImagesCount, imagesCount)
    }

    s.Require().Equal(messageCount, len(response.Messages()), "it returns the messages")
    s.Require().NoError(err)
    s.Require().Len(response.Messages(), messageCount)

    response, err = WaitOnMessengerResponse(
        theirMessenger,
        func(r *MessengerResponse) bool { return len(r.messages) == messageCount },
        "no messages",
    )

    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), messageCount)
    for _, message := range response.Messages() {
        image := message.GetImage()
        s.Require().NotNil(image, "Message.ID=%s", message.ID)
        s.Require().Equal(image.AlbumImagesCount, imagesCount)
        s.Require().NotEmpty(image.AlbumId, "Message.ID=%s", message.ID)
    }

    firstMessageID := response.Messages()[0].ID
    sendResponse, err := s.m.DeleteMessageAndSend(context.Background(), firstMessageID)

    s.Require().NoError(err)
    s.Require().Len(sendResponse.Messages(), 0)
    s.Require().Len(sendResponse.RemovedMessages(), 3)
    s.Require().Equal(sendResponse.RemovedMessages()[0].DeletedBy, "")
    s.Require().Len(sendResponse.Chats(), 1)

    // LastMessage marked as deleted
    s.Require().Equal(sendResponse.Chats()[0].LastMessage.ID, album[2].ID)
    s.Require().Equal(sendResponse.Chats()[0].LastMessage.Deleted, true)

    // Main instance user attempts to delete the message it received from theirMessenger
    _, err = theirMessenger.DeleteMessageAndSend(context.Background(), firstMessageID)

    s.Require().ErrorContains(err, "can't find chat")
}

func (s *MessengerRemoveMessageSuite) TestDeleteImageMessageFirstThenMessage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    contact, err := BuildContactFromPublicKey(&theirMessenger.identity.PublicKey)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)
    messageID1 := "message-id1"
    messageID2 := "message-id2"
    albumID := "album-id1"

    messageCount := 2
    var album []*common.Message
    for i := 0; i < messageCount; i++ {
        image, err := buildImageWithAlbumIDMessage(*ourChat, albumID)
        image.Clock = 1
        s.NoError(err)
        album = append(album, image)
    }

    deleteMessage := &DeleteMessage{
        DeleteMessage: &protobuf.DeleteMessage{
            Clock:       2,
            MessageType: protobuf.MessageType_ONE_TO_ONE,
            MessageId:   messageID1,
            ChatId:      theirChat.ID,
        },
        From: common.PubkeyToHex(&theirMessenger.identity.PublicKey),
    }

    state := &ReceivedMessageState{
        Response: &MessengerResponse{},
    }

    // Handle Delete first
    err = s.m.handleDeleteMessage(state, deleteMessage)
    s.Require().NoError(err)

    // Handle first image message
    state = &ReceivedMessageState{
        Response: &MessengerResponse{},
        CurrentMessageState: &CurrentMessageState{
            MessageID:        messageID1,
            WhisperTimestamp: s.m.getTimesource().GetCurrentTime(),
            Contact:          contact,
            PublicKey:        &theirMessenger.identity.PublicKey,
        },
    }
    err = s.m.HandleChatMessage(state, album[0].ChatMessage, nil, false)
    s.Require().NoError(err)
    s.Require().Len(state.Response.Messages(), 0) // Message should not be added to response
    s.Require().Len(state.Response.RemovedMessages(), 0)
    s.Require().Nil(state.Response.Chats()[0].LastMessage)

    // Handle second  image message
    state = &ReceivedMessageState{
        Response: &MessengerResponse{},
        CurrentMessageState: &CurrentMessageState{
            MessageID:        messageID2,
            WhisperTimestamp: s.m.getTimesource().GetCurrentTime(),
            Contact:          contact,
            PublicKey:        &theirMessenger.identity.PublicKey,
        },
    }
    err = s.m.HandleChatMessage(state, album[1].ChatMessage, nil, false)
    s.Require().NoError(err)
    s.Require().Len(state.Response.Messages(), 0) // Message should not be added to response even if we didn't delete that ID
    s.Require().Len(state.Response.RemovedMessages(), 0)
    s.Require().Nil(state.Response.Chats()[0].LastMessage)
}

func (s *MessengerRemoveMessageSuite) TestDeleteMessageWithAMention() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage := buildTestMessage(*theirChat)
    inputMessage.Text = "text with a mention @" + common.PubkeyToHex(&s.privateKey.PublicKey)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    messageID := sendResponse.Messages()[0].ID

    response, err := WaitOnMessengerResponse(
        s.m,
        func(r *MessengerResponse) bool { return len(r.messages) == 1 },
        "no messages",
    )
    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 1)
    // Receiver (us) is mentioned
    s.Require().Equal(int(response.Chats()[0].UnviewedMessagesCount), 1)
    s.Require().Equal(int(response.Chats()[0].UnviewedMentionsCount), 1)
    s.Require().True(response.Messages()[0].Mentioned)

    deleteMessage := &DeleteMessage{
        DeleteMessage: &protobuf.DeleteMessage{
            Clock:       2,
            MessageType: protobuf.MessageType_ONE_TO_ONE,
            MessageId:   messageID,
            ChatId:      theirChat.ID,
        },
        From: common.PubkeyToHex(&theirMessenger.identity.PublicKey),
    }

    state := &ReceivedMessageState{
        Response: &MessengerResponse{},
    }

    // Handle Delete first
    err = s.m.handleDeleteMessage(state, deleteMessage)

    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 1)
    // Receiver (us) is  no longer mentioned
    s.Require().Equal(int(state.Response.Chats()[0].UnviewedMessagesCount), 0)
    s.Require().Equal(int(state.Response.Chats()[0].UnviewedMentionsCount), 0)
}

// This test makes sure the UnviewMessageCount doesn't go below 0 in a very rare case where the Chat could be marked
// as read but the message still unseen (Seen == false)
func (s *MessengerRemoveMessageSuite) TestDeleteMessageAndChatIsAlreadyRead() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage := buildTestMessage(*theirChat)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    response, err := WaitOnMessengerResponse(
        s.m,
        func(r *MessengerResponse) bool { return len(r.messages) == 1 },
        "no messages",
    )
    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Equal(response.Chats()[0].UnviewedMessagesCount, uint(1))
    s.Require().Len(response.Messages(), 1)

    // Force UnviewedMessagesCount to 0 to test if the uint validation is done correctly
    ourChat.UnviewedMessagesCount = 0
    err = s.m.saveChat(ourChat)

    s.Require().NoError(err)

    ogMessage := sendResponse.Messages()[0]

    deleteMessage := &DeleteMessage{
        DeleteMessage: &protobuf.DeleteMessage{
            Clock:       2,
            MessageType: protobuf.MessageType_ONE_TO_ONE,
            MessageId:   ogMessage.ID,
            ChatId:      theirChat.ID,
        },
        From: common.PubkeyToHex(&theirMessenger.identity.PublicKey),
    }

    state := &ReceivedMessageState{
        Response: &MessengerResponse{},
    }

    // Handle Delete first
    err = s.m.handleDeleteMessage(state, deleteMessage)

    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 1)
    // Receiver (us) no longer has unread messages and it's not negative
    s.Require().Equal(0, int(state.Response.Chats()[0].UnviewedMessagesCount))
}

func (s *MessengerRemoveMessageSuite) TestDeleteMessageReplyToImage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage := buildTestMessage(*theirChat)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    response, err := WaitOnMessengerResponse(
        s.m,
        func(r *MessengerResponse) bool { return len(r.messages) == 1 },
        "no messages",
    )
    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 1)

    ogMessage := sendResponse.Messages()[0]

    // create an http server
    mediaServer, err := server.NewMediaServer(nil, nil, nil, nil)
    s.Require().NoError(err)
    s.Require().NotNil(mediaServer)
    s.Require().NoError(mediaServer.Start())

    theirMessenger.SetMediaServer(mediaServer)

    // We reply to our own message with an image
    imageMessage, err := buildImageWithoutAlbumIDMessage(*theirChat)
    s.NoError(err)

    imageMessage.ResponseTo = ogMessage.ID

    _, err = theirMessenger.SendChatMessages(context.Background(), []*common.Message{imageMessage})
    s.NoError(err)

    // We check that the URL is correctly returned
    sendResponse, err = theirMessenger.DeleteMessageAndSend(context.Background(), ogMessage.ID)

    s.Require().NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)
    s.Require().NotEmpty(sendResponse.Messages()[0].ImageLocalURL)
}

func (s *MessengerRemoveMessageSuite) TestDeleteMessageForMeReplyToImage() {
    theirMessenger := s.newMessenger()
    defer TearDownMessenger(&s.Suite, theirMessenger)

    theirChat := CreateOneToOneChat("Their 1TO1", &s.privateKey.PublicKey, s.m.transport)
    err := theirMessenger.SaveChat(theirChat)
    s.Require().NoError(err)

    ourChat := CreateOneToOneChat("Our 1TO1", &theirMessenger.identity.PublicKey, s.m.transport)
    err = s.m.SaveChat(ourChat)
    s.Require().NoError(err)

    inputMessage := buildTestMessage(*theirChat)
    sendResponse, err := theirMessenger.SendChatMessage(context.Background(), inputMessage)
    s.NoError(err)
    s.Require().Len(sendResponse.Messages(), 1)

    response, err := WaitOnMessengerResponse(
        s.m,
        func(r *MessengerResponse) bool { return len(r.messages) == 1 },
        "no messages",
    )
    s.Require().NoError(err)
    s.Require().Len(response.Chats(), 1)
    s.Require().Len(response.Messages(), 1)

    ogMessage := sendResponse.Messages()[0]

    // create an http server
    mediaServer, err := server.NewMediaServer(nil, nil, nil, nil)
    s.Require().NoError(err)
    s.Require().NotNil(mediaServer)
    s.Require().NoError(mediaServer.Start())

    theirMessenger.SetMediaServer(mediaServer)

    // We reply to our own message with an image
    imageMessage, err := buildImageWithoutAlbumIDMessage(*theirChat)
    s.NoError(err)

    imageMessage.ResponseTo = ogMessage.ID

    _, err = theirMessenger.SendChatMessages(context.Background(), []*common.Message{imageMessage})
    s.NoError(err)

    // We check that the URL is correctly returned
    sendResponse, err = theirMessenger.DeleteMessageForMeAndSync(context.Background(), theirChat.ID, ogMessage.ID)

    s.Require().NoError(err)
    messages := sendResponse.Messages()
    s.Require().Len(messages, 2)

    var deletedMessage, replyMessage *common.Message
    if messages[0].ID == ogMessage.ID {
        deletedMessage = messages[0]
        replyMessage = messages[1]
    } else {
        deletedMessage = messages[1]
        replyMessage = messages[0]
    }

    s.Require().True(deletedMessage.DeletedForMe)
    s.Require().NotEmpty(replyMessage.ImageLocalURL)
}