status-im/status-go

View on GitHub
protocol/activity_center_persistence_test.go

Summary

Maintainability
B
5 hrs
Test Coverage
package protocol

import (
    "strconv"
    "testing"
    "time"

    "github.com/stretchr/testify/suite"

    "github.com/status-im/status-go/eth-node/types"
    "github.com/status-im/status-go/protocol/common"
    "github.com/status-im/status-go/protocol/protobuf"
)

func TestActivityCenterPersistence(t *testing.T) {
    suite.Run(t, new(ActivityCenterPersistenceTestSuite))
}

type ActivityCenterPersistenceTestSuite struct {
    suite.Suite
    idCounter int
}

func (s *ActivityCenterPersistenceTestSuite) SetupTest() {
    s.idCounter = 0
}

func currentMilliseconds() uint64 {
    c := time.Now().UnixMilli()
    return uint64(c)
}

func (s *ActivityCenterPersistenceTestSuite) createNotifications(p *sqlitePersistence, notifications []*ActivityCenterNotification) []*ActivityCenterNotification {
    now := currentMilliseconds()
    for index, notif := range notifications {
        if notif.Timestamp == 0 {
            notif.Timestamp = now
        }
        if len(notif.ID) == 0 {
            s.idCounter++
            notif.ID = types.HexBytes(strconv.Itoa(s.idCounter + index))
        }
        if notif.UpdatedAt == 0 {
            notif.UpdatedAt = now
        }
        _, err := p.SaveActivityCenterNotification(notif, true)
        s.Require().NoError(err, notif.ID)
    }

    // Fetches notifications to get an up-to-date slice.
    var createdNotifications []*ActivityCenterNotification
    for _, notif := range notifications {
        n, err := p.GetActivityCenterNotificationByID(notif.ID)
        s.Require().NoError(err, notif.ID)
        createdNotifications = append(createdNotifications, n)
    }

    return createdNotifications
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsWhenEmpty() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    message := common.NewMessage()
    message.Text = "sample text"
    chat.LastMessage = message
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    s.createNotifications(p, []*ActivityCenterNotification{
        {
            Type: ActivityCenterNotificationTypeMention,
        },
    })

    var count uint64
    count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().Equal(uint64(1), count)

    _, err = p.MarkActivityCenterNotificationsDeleted([]types.HexBytes{}, currentMilliseconds())
    s.Require().NoError(err)

    count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().Equal(uint64(1), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsWithMultipleIds() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    message := common.NewMessage()
    message.Text = "sample text"
    chat.LastMessage = message
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    notifications := s.createNotifications(p, []*ActivityCenterNotification{
        {Type: ActivityCenterNotificationTypeMention},
        {Type: ActivityCenterNotificationTypeNewOneToOne},
        {Type: ActivityCenterNotificationTypeNewOneToOne},
    })

    var count uint64
    count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().Equal(uint64(3), count)

    _, err = p.MarkActivityCenterNotificationsDeleted([]types.HexBytes{notifications[1].ID, notifications[2].ID}, currentMilliseconds())
    s.Require().NoError(err)

    count, _ = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().Equal(uint64(1), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsForMessage() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    chat2 := CreatePublicChat("test-chat", &testTimeSource{})
    err = p.SaveChat(*chat2)
    s.Require().NoError(err)

    messages := []*common.Message{
        {
            ID:          "0x1",
            ChatMessage: &protobuf.ChatMessage{},
            LocalChatID: chat.ID,
        },
        {
            ID:          "0x2",
            ChatMessage: &protobuf.ChatMessage{},
            LocalChatID: chat.ID,
        },
        {
            ChatMessage: &protobuf.ChatMessage{},
            ID:          "0x3",
        },
    }
    err = p.SaveMessages(messages)
    s.Require().NoError(err)

    chat.LastMessage = messages[1]
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    chatMessages, _, err := p.MessageByChatID(chat.ID, "", 2)
    s.Require().NoError(err)
    s.Require().Len(chatMessages, 2)

    nID1 := types.HexBytes("1")
    nID2 := types.HexBytes("2")
    nID3 := types.HexBytes("3")
    nID4 := types.HexBytes("4")

    s.createNotifications(p, []*ActivityCenterNotification{
        {
            ID:      nID1,
            ChatID:  chat.ID,
            Type:    ActivityCenterNotificationTypeMention,
            Message: messages[0],
        },
        {
            ID:      nID2,
            ChatID:  chat.ID,
            Type:    ActivityCenterNotificationTypeMention,
            Message: messages[1],
        },
        {
            ID:     nID3,
            ChatID: chat.ID,
            Type:   ActivityCenterNotificationTypeMention,
        },
        {
            ID:   nID4,
            Type: ActivityCenterNotificationTypeMention,
        },
    })

    // Test: soft delete only the notifications that have Message.ID == messages[0].ID.
    _, err = p.DeleteActivityCenterNotificationForMessage(chat.ID, messages[0].ID, currentMilliseconds())
    s.Require().NoError(err)

    notif, err := p.GetActivityCenterNotificationByID(nID1)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().True(notif.Deleted)
    s.Require().True(notif.Dismissed)
    s.Require().True(notif.Read)

    // Other notifications are not affected.
    for _, id := range []types.HexBytes{nID2, nID3, nID4} {
        notif, err = p.GetActivityCenterNotificationByID(id)
        s.Require().NoError(err)
        s.Require().NotNil(notif)
        s.Require().False(notif.Deleted, notif.ID)
        s.Require().False(notif.Dismissed, notif.ID)
        s.Require().False(notif.Read, notif.ID)
    }

    // Test: soft delete the notifications that have Message.ID == messages[1].ID
    _, err = p.DeleteActivityCenterNotificationForMessage(chat.ID, messages[1].ID, currentMilliseconds())
    s.Require().NoError(err)

    notif, err = p.GetActivityCenterNotificationByID(nID2)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().True(notif.Deleted, notif.ID)
    s.Require().True(notif.Dismissed, notif.ID)
    s.Require().True(notif.Read, notif.ID)

    // Check that notifications with LastMessage.ID == messages[1].ID will remain.
    notif, err = p.GetActivityCenterNotificationByID(nID3)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().False(notif.Deleted, notif.ID)
    s.Require().False(notif.Dismissed, notif.ID)
    s.Require().False(notif.Read, notif.ID)

    notif, err = p.GetActivityCenterNotificationByID(nID4)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().False(notif.Deleted)
    s.Require().False(notif.Dismissed)
    s.Require().False(notif.Read)

    // Test: don't do anything if passed a chat and message without notifications.
    _, err = p.DeleteActivityCenterNotificationForMessage(chat2.ID, messages[2].ID, currentMilliseconds())
    s.Require().NoError(err)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DeleteActivityCenterNotificationsForMessage_LastMessage() {
    // Create the temporary test-database that will be used to store chats, messages, and notifications
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    // Create and save the public chat that will be used to group our test messages
    chat := CreatePublicChat("test-chat", &testTimeSource{})
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    // Define multiple test messages for our chat so we can emulate a chat with a latest message.
    messages := []*common.Message{
        {
            ID:          "0x1",
            ChatMessage: &protobuf.ChatMessage{},
            LocalChatID: chat.ID,
        },
        {
            ID:          "0x2",
            ChatMessage: &protobuf.ChatMessage{},
            LocalChatID: chat.ID,
        },
    }
    err = p.SaveMessages(messages)
    s.Require().NoError(err)

    chat.LastMessage = messages[1]
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    chatMessages, _, err := p.MessageByChatID(chat.ID, "", 2)
    s.Require().NoError(err)
    s.Require().Len(chatMessages, 2)

    // Define multiple notifications of different types to emulate the removal of notifications when deleting a message
    nID1 := types.HexBytes("1")
    nID2 := types.HexBytes("2")
    nID3 := types.HexBytes("3")
    nID4 := types.HexBytes("4")

    s.createNotifications(p, []*ActivityCenterNotification{
        {
            ID:          nID1,
            ChatID:      chat.ID,
            Type:        ActivityCenterNotificationTypeMention,
            Message:     messages[0],
            LastMessage: messages[1],
        },
        {
            ID:      nID2,
            ChatID:  chat.ID,
            Type:    ActivityCenterNotificationTypeMention,
            Message: messages[1],
        },
        {
            ID:          nID3,
            ChatID:      chat.ID,
            Type:        ActivityCenterNotificationTypeNewOneToOne,
            LastMessage: messages[1],
        },
        {
            ID:          nID4,
            ChatID:      chat.ID,
            Type:        ActivityCenterNotificationTypeNewPrivateGroupChat,
            LastMessage: messages[1],
        },
    })

    // Action: soft delete notifications related to a chat and message
    _, err = p.DeleteActivityCenterNotificationForMessage(chat.ID, messages[1].ID, currentMilliseconds())
    s.Require().NoError(err)

    // Test: check that notifications unrelated to the message are not affected.
    notif, err := p.GetActivityCenterNotificationByID(nID1)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().False(notif.Deleted, notif.ID)
    s.Require().False(notif.Dismissed, notif.ID)
    s.Require().False(notif.Read, notif.ID)

    // Test: check notifications directly related to the message are soft deleted
    notif, err = p.GetActivityCenterNotificationByID(nID2)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().True(notif.Deleted)
    s.Require().True(notif.Dismissed)
    s.Require().True(notif.Read)

    // Test: check NewOneToOne or NewPrivateGroupChat notifications that are indirectly related to the message are soft deleted
    for _, id := range []types.HexBytes{nID3, nID4} {
        notif, err = p.GetActivityCenterNotificationByID(id)
        s.Require().NoError(err)
        s.Require().NotNil(notif)
        s.Require().False(notif.Deleted)
        s.Require().False(notif.Dismissed)
        s.Require().False(notif.Read)
    }
}

func (s *ActivityCenterPersistenceTestSuite) Test_AcceptActivityCenterNotificationsForInvitesFromUser() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    nID1 := types.HexBytes("1")
    nID2 := types.HexBytes("2")
    nID3 := types.HexBytes("3")
    nID4 := types.HexBytes("4")

    userPublicKey := "zQ3sh"

    notifications := []*ActivityCenterNotification{
        {
            ID:        nID1,
            Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
            Timestamp: 1,
        },
        {
            ID:        nID2,
            Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
            Timestamp: 1,
            Author:    userPublicKey,
        },
        {
            ID:        nID3,
            Type:      ActivityCenterNotificationTypeMention,
            Timestamp: 1,
            Author:    userPublicKey,
        },
        {
            ID:        nID4,
            Timestamp: 1,
            Type:      ActivityCenterNotificationTypeMention,
        },
    }

    var notif *ActivityCenterNotification
    for _, notif = range notifications {
        _, err = p.SaveActivityCenterNotification(notif, true)
        s.Require().NoError(err, notif.ID)
    }

    // Only notifications of type new private group chat and with Author equal to
    // userPublicKey should be marked as accepted & read.
    _, err = p.GetActivityCenterNotificationByID(nID2)
    s.Require().NoError(err)
    s.Require().False(notifications[0].Accepted)
    s.Require().False(notifications[0].Read)

    notifications, err = p.AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey, 1)
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID2, notifications[0].ID)

    notif, err = p.GetActivityCenterNotificationByID(nID2)
    s.Require().NoError(err)
    s.Require().True(notif.Accepted)
    s.Require().True(notif.Read)

    // Deleted notifications are ignored.
    notif = &ActivityCenterNotification{
        ID:        types.HexBytes("99"),
        Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
        Timestamp: 1,
        Author:    userPublicKey,
        Deleted:   true,
    }
    _, err = p.SaveActivityCenterNotification(notif, true)
    s.Require().NoError(err)
    _, err = p.AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey, currentMilliseconds())
    s.Require().NoError(err)
    notif, err = p.GetActivityCenterNotificationByID(notif.ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().True(notif.Deleted)

    // Dismissed notifications are ignored.
    notif = &ActivityCenterNotification{
        ID:        types.HexBytes("100"),
        Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
        Timestamp: 1,
        Author:    userPublicKey,
        Dismissed: true,
    }
    _, err = p.SaveActivityCenterNotification(notif, true)
    s.Require().NoError(err)
    _, err = p.AcceptActivityCenterNotificationsForInvitesFromUser(userPublicKey, currentMilliseconds())
    s.Require().NoError(err)
    notif, err = p.GetActivityCenterNotificationByID(notif.ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().True(notif.Dismissed)
}

func (s *ActivityCenterPersistenceTestSuite) Test_GetToProcessActivityCenterNotificationIds() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    notifications := s.createNotifications(p, []*ActivityCenterNotification{
        {
            Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
            Deleted: true,
        },
        {
            Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
            Dismissed: true,
        },
        {
            Type:     ActivityCenterNotificationTypeMention,
            Accepted: true,
        },
        {
            Type: ActivityCenterNotificationTypeMention,
        },
    })

    ids, err := p.GetToProcessActivityCenterNotificationIds()
    s.Require().NoError(err)
    s.Require().Len(ids, 1)
    s.Require().Equal(notifications[3].ID, types.HexBytes(ids[0]))
}

func (s *ActivityCenterPersistenceTestSuite) Test_HasPendingNotificationsForChat() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    // Test: there are no notifications.
    result, err := p.HasPendingNotificationsForChat(chat.ID)
    s.Require().NoError(err)
    s.Require().False(result)

    // Test: there are only deleted, dismissed or accepted notifications,
    // therefore, no pending notifications.
    s.createNotifications(p, []*ActivityCenterNotification{
        {
            ChatID:  chat.ID,
            Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
            Deleted: true,
        },
        {
            ChatID:    chat.ID,
            Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
            Dismissed: true,
        },
        {
            ChatID:   chat.ID,
            Type:     ActivityCenterNotificationTypeMention,
            Accepted: true,
        },
    })

    result, err = p.HasPendingNotificationsForChat(chat.ID)
    s.Require().NoError(err)
    s.Require().False(result)

    // Test: there's one pending notification.
    notif := &ActivityCenterNotification{
        ID:        types.HexBytes("99"),
        ChatID:    chat.ID,
        Type:      ActivityCenterNotificationTypeCommunityRequest,
        Timestamp: 1,
    }
    _, err = p.SaveActivityCenterNotification(notif, true)
    s.Require().NoError(err)

    result, err = p.HasPendingNotificationsForChat(chat.ID)
    s.Require().NoError(err)
    s.Require().True(result)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DismissAllActivityCenterNotificationsFromUser() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    publicKey := "0x04"

    notifications := s.createNotifications(p, []*ActivityCenterNotification{
        {
            Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
            Author:  publicKey,
            Deleted: true,
        },
        {
            Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
            Author:    publicKey,
            Dismissed: true,
        },
        {
            Type:     ActivityCenterNotificationTypeMention,
            Author:   publicKey,
            Accepted: true,
        },
        {
            Type:   ActivityCenterNotificationTypeMention,
            Author: "0x09",
        },
        {
            Type:   ActivityCenterNotificationTypeMention,
            Author: publicKey,
        },
    })

    _, err = p.DismissAllActivityCenterNotificationsFromUser(publicKey, 1)
    s.Require().NoError(err)

    // Ignores already soft deleted.
    notif, err := p.GetActivityCenterNotificationByID(notifications[0].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().True(notif.Deleted)

    // Ignores already dismissed.
    notif, err = p.GetActivityCenterNotificationByID(notifications[1].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().True(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Ignores already accepted.
    notif, err = p.GetActivityCenterNotificationByID(notifications[2].ID)
    s.Require().NoError(err)
    s.Require().True(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Ignores notification from different author.
    notif, err = p.GetActivityCenterNotificationByID(notifications[3].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Finally, dismiss and mark as read this one notification.
    notif, err = p.GetActivityCenterNotificationByID(notifications[4].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().True(notif.Read)
    s.Require().True(notif.Dismissed)
    s.Require().False(notif.Deleted)
}

func (s *ActivityCenterPersistenceTestSuite) Test_DismissAllActivityCenterNotificationsFromChatID() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chatID := "0x99"

    notifications := s.createNotifications(p, []*ActivityCenterNotification{
        {
            ChatID:  chatID,
            Type:    ActivityCenterNotificationTypeNewPrivateGroupChat,
            Deleted: true,
        },
        {
            ChatID:    chatID,
            Type:      ActivityCenterNotificationTypeNewPrivateGroupChat,
            Dismissed: true,
        },
        {
            ChatID:   chatID,
            Type:     ActivityCenterNotificationTypeMention,
            Accepted: true,
        },
        {
            Type: ActivityCenterNotificationTypeMention,
        },
        {
            ChatID: chatID,
            Type:   ActivityCenterNotificationTypeContactRequest,
        },
        {
            ChatID: chatID,
            Type:   ActivityCenterNotificationTypeMention,
        },
    })

    _, err = p.DismissAllActivityCenterNotificationsFromChatID(chatID, currentMilliseconds())
    s.Require().NoError(err)

    // Ignores already soft deleted.
    notif, err := p.GetActivityCenterNotificationByID(notifications[0].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().True(notif.Deleted)

    // Do not ignore already dismissed, because notifications can become
    // read/unread AND dismissed, and the method should still update the Read
    // column.
    notif, err = p.GetActivityCenterNotificationByID(notifications[1].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().True(notif.Read)
    s.Require().True(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Ignores already accepted.
    notif, err = p.GetActivityCenterNotificationByID(notifications[2].ID)
    s.Require().NoError(err)
    s.Require().True(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Ignores notification from different chat.
    notif, err = p.GetActivityCenterNotificationByID(notifications[3].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Ignores contact request notifications.
    notif, err = p.GetActivityCenterNotificationByID(notifications[4].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().False(notif.Read)
    s.Require().False(notif.Dismissed)
    s.Require().False(notif.Deleted)

    // Finally, dismiss and mark as read this one notification.
    notif, err = p.GetActivityCenterNotificationByID(notifications[5].ID)
    s.Require().NoError(err)
    s.Require().False(notif.Accepted)
    s.Require().True(notif.Read)
    s.Require().True(notif.Dismissed)
    s.Require().False(notif.Deleted)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActiveContactRequestNotification() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    contactID := "0x99"

    // Test: ignores deleted/dismissed/accepted notifications, as well as
    // notifications not associated to any chat.
    s.createNotifications(p, []*ActivityCenterNotification{
        {
            ChatID:  chat.ID,
            Author:  contactID,
            Type:    ActivityCenterNotificationTypeContactRequest,
            Deleted: true,
        },
        {
            ChatID:    chat.ID,
            Author:    contactID,
            Type:      ActivityCenterNotificationTypeContactRequest,
            Dismissed: true,
        },
        {
            ChatID:   chat.ID,
            Author:   contactID,
            Type:     ActivityCenterNotificationTypeContactRequest,
            Accepted: true,
        },
    })

    notif, err := p.ActiveContactRequestNotification(contactID)
    s.Require().NoError(err)
    s.Require().Nil(notif)

    // Test: Ignores notifications that are not contact requests.
    s.createNotifications(p, []*ActivityCenterNotification{
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityInvitation},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityKicked},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityMembershipRequest},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeCommunityRequest},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeContactVerification},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeMention},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeNewOneToOne},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeNewPrivateGroupChat},
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeReply},
    })

    notif, err = p.ActiveContactRequestNotification(contactID)
    s.Require().NoError(err)
    s.Require().Nil(notif)

    // Test: Returns one, and only one contact request notification for the
    // contact under test.
    s.createNotifications(p, []*ActivityCenterNotification{
        {ChatID: chat.ID, Author: contactID, Type: ActivityCenterNotificationTypeContactRequest},
    })

    notif, err = p.ActiveContactRequestNotification(contactID)
    s.Require().NoError(err)
    s.Require().NotNil(notif)
    s.Require().Equal(ActivityCenterNotificationTypeContactRequest, notif.Type)

    // Test: In case there's more than one notification, return the most recent
    // one according to the notification's timestamp.
    expectedID := types.HexBytes("667")

    t1 := currentMilliseconds()
    t2 := currentMilliseconds()
    s.createNotifications(p, []*ActivityCenterNotification{
        {
            ID:        expectedID,
            Timestamp: t2 + 1,
            ChatID:    chat.ID,
            Author:    contactID,
            Type:      ActivityCenterNotificationTypeContactRequest,
        },
        {
            ID:        types.HexBytes("666"),
            Timestamp: t1,
            ChatID:    chat.ID,
            Author:    contactID,
            Type:      ActivityCenterNotificationTypeContactRequest,
        },
    })

    notif, err = p.ActiveContactRequestNotification(contactID)
    s.Require().NoError(err)
    s.Require().Equal(expectedID, notif.ID)
}

func (s *ActivityCenterPersistenceTestSuite) Test_UnreadActivityCenterNotificationsCount() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    s.createNotifications(p, []*ActivityCenterNotification{
        {Type: ActivityCenterNotificationTypeMention, Read: true},
        {Type: ActivityCenterNotificationTypeNewOneToOne, Deleted: true},
        {Type: ActivityCenterNotificationTypeMention, Dismissed: true},
        {Type: ActivityCenterNotificationTypeCommunityRequest, Accepted: true},
        {Type: ActivityCenterNotificationTypeContactRequest},
    })

    // Test: Ignore soft deleted and accepted.
    count, err := p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, true)
    s.Require().NoError(err)
    s.Require().Equal(uint64(3), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_UnreadAndAcceptedActivityCenterNotificationsCount() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    s.createNotifications(p, []*ActivityCenterNotification{
        {Type: ActivityCenterNotificationTypeMention, Read: true},
        {Type: ActivityCenterNotificationTypeNewOneToOne, Deleted: true},
        {Type: ActivityCenterNotificationTypeMention, Dismissed: true},
        {Type: ActivityCenterNotificationTypeCommunityRequest, Accepted: true},
        {Type: ActivityCenterNotificationTypeContactRequest},
    })

    // Test: counts everything, except soft deleted notifications.
    count, err := p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, true)
    s.Require().NoError(err)
    s.Require().Equal(uint64(3), count)

    // Test: counts everything, except soft deleted ones and limit by type.
    count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{
        ActivityCenterNotificationTypeContactRequest,
    }, ActivityCenterQueryParamsReadUnread, true)
    s.Require().NoError(err)
    s.Require().Equal(uint64(1), count)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterPersistence() {
    nID1 := types.HexBytes([]byte("1"))
    nID2 := types.HexBytes([]byte("2"))
    nID3 := types.HexBytes([]byte("3"))
    nID4 := types.HexBytes([]byte("4"))

    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    message := common.NewMessage()
    message.Text = "sample text"
    chat.LastMessage = message
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    notification := &ActivityCenterNotification{
        ID:        nID1,
        Type:      ActivityCenterNotificationTypeNewOneToOne,
        ChatID:    chat.ID,
        Timestamp: 1,
    }
    _, err = p.SaveActivityCenterNotification(notification, true)
    s.Require().NoError(err)

    cursor, notifications, err := p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)
    s.Require().Empty(cursor)
    s.Require().Len(notifications, 1)
    s.Require().Equal(chat.ID, notifications[0].ChatID)
    s.Require().Equal(message, notifications[0].LastMessage)

    // Add another notification

    notification = &ActivityCenterNotification{
        ID:        nID2,
        Type:      ActivityCenterNotificationTypeNewOneToOne,
        Timestamp: 2,
    }
    _, err = p.SaveActivityCenterNotification(notification, true)
    s.Require().NoError(err)

    cursor, notifications, err = p.ActivityCenterNotifications("", 1, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().NotEmpty(cursor)
    s.Require().Equal(nID2, notifications[0].ID)

    // fetch next pagination

    cursor, notifications, err = p.ActivityCenterNotifications(cursor, 1, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Empty(cursor)
    s.Require().False(notifications[0].Read)
    s.Require().Equal(nID1, notifications[0].ID)

    // Check count
    count, err := p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().NoError(err)
    s.Require().Equal(uint64(2), count)

    var updatedAt uint64 = 1
    // Mark first one as read
    s.Require().NoError(p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID1}, updatedAt))
    count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().NoError(err)
    s.Require().Equal(uint64(1), count)

    // Mark first one as unread
    updatedAt++
    _, err = p.MarkActivityCenterNotificationsUnread([]types.HexBytes{nID1}, updatedAt)
    s.Require().NoError(err)
    count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().NoError(err)
    s.Require().Equal(uint64(2), count)

    // Mark all read
    updatedAt++
    s.Require().NoError(p.MarkAllActivityCenterNotificationsRead(updatedAt))
    _, notifications, err = p.ActivityCenterNotifications(cursor, 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)
    s.Require().Len(notifications, 2)
    s.Require().Empty(cursor)
    s.Require().True(notifications[0].Read)
    s.Require().True(notifications[1].Read)

    // Check count
    count, err = p.ActivityCenterNotificationsCount([]ActivityCenterType{}, ActivityCenterQueryParamsReadUnread, false)
    s.Require().NoError(err)
    s.Require().Equal(uint64(0), count)

    // Mark first one as accepted
    updatedAt++
    notifications, err = p.AcceptActivityCenterNotifications([]types.HexBytes{nID1}, updatedAt)
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    _, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)
    // It should not be returned anymore
    s.Require().Len(notifications, 1)

    // Mark last one as dismissed
    updatedAt++
    s.Require().NoError(p.DismissActivityCenterNotifications([]types.HexBytes{nID2}, updatedAt))
    _, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)

    s.Require().Len(notifications, 1)
    s.Require().True(notifications[0].Dismissed)

    // Insert new notification
    notification = &ActivityCenterNotification{
        ID:        nID3,
        Type:      ActivityCenterNotificationTypeNewOneToOne,
        Timestamp: 3,
    }
    _, err = p.SaveActivityCenterNotification(notification, true)
    s.Require().NoError(err)

    // Mark all as accepted
    updatedAt++
    notifications, err = p.AcceptAllActivityCenterNotifications(updatedAt)
    s.Require().NoError(err)
    s.Require().Len(notifications, 2)

    _, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)

    s.Require().Len(notifications, 1)

    // Insert new notification
    notification = &ActivityCenterNotification{
        ID:        nID4,
        Type:      ActivityCenterNotificationTypeNewOneToOne,
        Timestamp: 4,
    }
    _, err = p.SaveActivityCenterNotification(notification, true)
    s.Require().NoError(err)

    // Mark all as dismissed
    updatedAt++
    s.Require().NoError(p.DismissAllActivityCenterNotifications(updatedAt))
    _, notifications, err = p.ActivityCenterNotifications("", 2, []ActivityCenterType{}, ActivityCenterQueryParamsReadAll, false)
    s.Require().NoError(err)

    s.Require().Len(notifications, 2)
    s.Require().True(notifications[0].Dismissed)
    s.Require().True(notifications[1].Dismissed)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterReadUnreadPagination() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    initialOrFinalCursor := ""

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    message := common.NewMessage()
    message.Text = "sample text"
    chat.LastMessage = message
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    nID1 := types.HexBytes("1")
    nID2 := types.HexBytes("2")
    nID3 := types.HexBytes("3")
    nID4 := types.HexBytes("4")
    nID5 := types.HexBytes("5")

    allNotifications := []*ActivityCenterNotification{
        {
            ID:        nID1,
            Type:      ActivityCenterNotificationTypeNewOneToOne,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
        {
            ID:        nID2,
            Type:      ActivityCenterNotificationTypeNewOneToOne,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
        {
            ID:        nID3,
            Type:      ActivityCenterNotificationTypeNewOneToOne,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
        {
            ID:        nID4,
            Type:      ActivityCenterNotificationTypeNewOneToOne,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
        {
            ID:        nID5,
            Type:      ActivityCenterNotificationTypeNewOneToOne,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
    }

    for _, notification := range allNotifications {
        _, err = p.SaveActivityCenterNotification(notification, true)
        s.Require().NoError(err)
    }

    // Mark the notification as read
    err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID2}, currentMilliseconds())
    s.Require().NoError(err)
    err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID4}, currentMilliseconds())
    s.Require().NoError(err)

    // Fetch UNREAD notifications, first page.
    cursor, notifications, err := p.ActivityCenterNotifications(
        initialOrFinalCursor,
        1,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID5, notifications[0].ID)
    s.Require().NotEmpty(cursor)

    // Fetch next pages.
    cursor, notifications, err = p.ActivityCenterNotifications(
        cursor,
        1,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID3, notifications[0].ID)
    s.Require().NotEmpty(cursor)

    cursor, notifications, err = p.ActivityCenterNotifications(
        cursor,
        1,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID1, notifications[0].ID)
    s.Require().Empty(cursor)

    // Fetch READ notifications, first page.
    cursor, notifications, err = p.ActivityCenterNotifications(
        initialOrFinalCursor,
        1,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadRead,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID4, notifications[0].ID)
    s.Require().NotEmpty(cursor)

    // Fetch next page.
    cursor, notifications, err = p.ActivityCenterNotifications(
        cursor,
        1,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadRead,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID2, notifications[0].ID)
    s.Require().Empty(cursor)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterReadUnreadFilterByTypes() {
    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    message := common.NewMessage()
    message.Text = "sample text"
    chat.LastMessage = message
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    initialCursor := ""
    limit := uint64(3)

    nID1 := types.HexBytes("1")
    nID2 := types.HexBytes("2")
    nID3 := types.HexBytes("3")

    allNotifications := []*ActivityCenterNotification{
        {
            ID:        nID1,
            Type:      ActivityCenterNotificationTypeMention,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
        {
            ID:        nID2,
            Type:      ActivityCenterNotificationTypeNewOneToOne,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
        {
            ID:        nID3,
            Type:      ActivityCenterNotificationTypeMention,
            ChatID:    chat.ID,
            Timestamp: 1,
        },
    }

    for _, notification := range allNotifications {
        _, err = p.SaveActivityCenterNotification(notification, true)
        s.Require().NoError(err)
    }

    // Don't filter by type if the array of types is empty.
    _, notifications, err := p.ActivityCenterNotifications(
        initialCursor,
        limit,
        []ActivityCenterType{},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 3)
    s.Require().Equal(nID3, notifications[0].ID)
    s.Require().Equal(nID2, notifications[1].ID)
    s.Require().Equal(nID1, notifications[2].ID)

    _, notifications, err = p.ActivityCenterNotifications(
        initialCursor,
        limit,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID2, notifications[0].ID)

    _, notifications, err = p.ActivityCenterNotifications(
        initialCursor,
        limit,
        []ActivityCenterType{ActivityCenterNotificationTypeMention},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 2)
    s.Require().Equal(nID3, notifications[0].ID)
    s.Require().Equal(nID1, notifications[1].ID)

    _, notifications, err = p.ActivityCenterNotifications(
        initialCursor,
        limit,
        []ActivityCenterType{ActivityCenterNotificationTypeMention, ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 3)
    s.Require().Equal(nID3, notifications[0].ID)
    s.Require().Equal(nID2, notifications[1].ID)
    s.Require().Equal(nID1, notifications[2].ID)

    // Mark all notifications as read.
    for _, notification := range allNotifications {
        err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{notification.ID}, currentMilliseconds())
        s.Require().NoError(err)
    }

    _, notifications, err = p.ActivityCenterNotifications(
        initialCursor,
        limit,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadRead,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID2, notifications[0].ID)

    _, notifications, err = p.ActivityCenterNotifications(
        initialCursor,
        limit,
        []ActivityCenterType{ActivityCenterNotificationTypeMention},
        ActivityCenterQueryParamsReadRead,
        false,
    )
    s.Require().NoError(err)
    s.Require().Len(notifications, 2)
    s.Require().Equal(nID3, notifications[0].ID)
    s.Require().Equal(nID1, notifications[1].ID)
}

func (s *ActivityCenterPersistenceTestSuite) Test_ActivityCenterReadUnread() {
    nID1 := types.HexBytes("1")
    nID2 := types.HexBytes("2")

    db, err := openTestDB()
    s.Require().NoError(err)
    p := newSQLitePersistence(db)

    chat := CreatePublicChat("test-chat", &testTimeSource{})
    message := common.NewMessage()
    message.Text = "sample text"
    chat.LastMessage = message
    err = p.SaveChat(*chat)
    s.Require().NoError(err)

    notification := &ActivityCenterNotification{
        ID:        nID1,
        Type:      ActivityCenterNotificationTypeNewOneToOne,
        ChatID:    chat.ID,
        Timestamp: 1,
    }

    _, err = p.SaveActivityCenterNotification(notification, true)
    s.Require().NoError(err)

    notification = &ActivityCenterNotification{
        ID:        nID2,
        Type:      ActivityCenterNotificationTypeNewOneToOne,
        ChatID:    chat.ID,
        Timestamp: 1,
    }

    _, err = p.SaveActivityCenterNotification(notification, true)
    s.Require().NoError(err)

    // Mark the notification as read
    err = p.MarkActivityCenterNotificationsRead([]types.HexBytes{nID2}, currentMilliseconds())
    s.Require().NoError(err)

    cursor, notifications, err := p.ActivityCenterNotifications(
        "",
        2,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadUnread,
        false,
    )
    s.Require().NoError(err)
    s.Require().Empty(cursor)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID1, notifications[0].ID)

    cursor, notifications, err = p.ActivityCenterNotifications(
        "",
        2,
        []ActivityCenterType{ActivityCenterNotificationTypeNewOneToOne},
        ActivityCenterQueryParamsReadRead,
        false,
    )
    s.Require().NoError(err)
    s.Require().Empty(cursor)
    s.Require().Len(notifications, 1)
    s.Require().Equal(nID2, notifications[0].ID)
}