status-go/protocol/persistence_test.go
Andrea Maria Piana 14e3eafaeb Handle async raw message confirmations
Sometimes confirmation for raw messages are received before the record
is actually saved in the database.

In this case, the code will preserve the Sent status.
2023-06-26 13:36:20 +01:00

1747 lines
44 KiB
Go

package protocol
import (
"bytes"
"database/sql"
"fmt"
"io/ioutil"
"math"
"sort"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/status-im/status-go/eth-node/crypto"
"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"
"github.com/status-im/status-go/protocol/sqlite"
)
func TestTableUserMessagesAllFieldsCount(t *testing.T) {
db := sqlitePersistence{}
expected := len(strings.Split(db.tableUserMessagesAllFields(), ","))
require.Equal(t, expected, db.tableUserMessagesAllFieldsCount())
}
func TestSaveMessages(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
for i := 0; i < 10; i++ {
id := strconv.Itoa(i)
err := insertMinimalMessage(p, id)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.EqualValues(t, id, m.ID)
}
}
func TestMessagesByIDs(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
var ids []string
for i := 0; i < 10; i++ {
id := strconv.Itoa(i)
err := insertMinimalMessage(p, id)
require.NoError(t, err)
ids = append(ids, id)
}
m, err := p.MessagesByIDs(ids)
require.NoError(t, err)
require.Len(t, m, 10)
}
func TestMessagesByIDs_WithDiscordMessagesPayload(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
var ids []string
for i := 0; i < 10; i++ {
id := strconv.Itoa(i)
err := insertMinimalMessage(p, id)
require.NoError(t, err)
err = insertMinimalDiscordMessage(p, id, id)
require.NoError(t, err)
ids = append(ids, id)
}
m, err := p.MessagesByIDs(ids)
require.NoError(t, err)
require.Len(t, m, 10)
for _, _m := range m {
require.NotNil(t, _m.GetDiscordMessage())
}
}
func TestMessageByID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "1"
err = insertMinimalMessage(p, id)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.EqualValues(t, id, m.ID)
}
func TestMessageByID_WithDiscordMessagePayload(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "1"
discordMessageID := "2"
err = insertMinimalDiscordMessage(p, id, discordMessageID)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.EqualValues(t, id, m.ID)
require.NotNil(t, m.GetDiscordMessage())
require.EqualValues(t, discordMessageID, m.GetDiscordMessage().Id)
require.EqualValues(t, "2", m.GetDiscordMessage().Author.Id)
}
func TestMessageByID_WithDiscordMessageAttachmentPayload(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "1"
discordMessageID := "2"
err = insertDiscordMessageWithAttachments(p, id, discordMessageID)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.EqualValues(t, id, m.ID)
dm := m.GetDiscordMessage()
require.NotNil(t, dm)
require.EqualValues(t, discordMessageID, dm.Id)
require.NotNil(t, dm.Attachments)
require.Len(t, dm.Attachments, 2)
}
func TestMessagesExist(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
err = insertMinimalMessage(p, "1")
require.NoError(t, err)
result, err := p.MessagesExist([]string{"1"})
require.NoError(t, err)
require.True(t, result["1"])
err = insertMinimalMessage(p, "2")
require.NoError(t, err)
result, err = p.MessagesExist([]string{"1", "2", "3"})
require.NoError(t, err)
require.True(t, result["1"])
require.True(t, result["2"])
require.False(t, result["3"])
}
func TestMessageByChatID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chatID := testPublicChatID
count := 1000
pageSize := 50
var messages []*common.Message
for i := 0; i < count; i++ {
messages = append(messages, &common.Message{
ID: strconv.Itoa(i),
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
},
From: testPK,
})
// Add some other chats.
if count%5 == 0 {
messages = append(messages, &common.Message{
ID: strconv.Itoa(count + i),
LocalChatID: "other-chat",
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
},
From: testPK,
})
}
}
// Add some out-of-order message. Add more than page size.
outOfOrderCount := pageSize + 1
allCount := count + outOfOrderCount
for i := 0; i < pageSize+1; i++ {
messages = append(messages, &common.Message{
ID: strconv.Itoa(count*2 + i),
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
},
From: testPK,
})
}
err = p.SaveMessages(messages)
require.NoError(t, err)
var (
result []*common.Message
cursor string
iter int
)
for {
var (
items []*common.Message
err error
)
items, cursor, err = p.MessageByChatID(chatID, cursor, pageSize)
require.NoError(t, err)
result = append(result, items...)
iter++
if len(cursor) == 0 || iter > count {
break
}
}
require.Equal(t, "", cursor) // for loop should exit because of cursor being empty
require.EqualValues(t, math.Ceil(float64(allCount)/float64(pageSize)), iter)
require.Equal(t, len(result), allCount)
require.True(
t,
// Verify descending order.
sort.SliceIsSorted(result, func(i, j int) bool {
return result[i].Clock > result[j].Clock
}),
)
}
func TestFirstUnseenMessageIDByChatID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
messageID, err := p.FirstUnseenMessageID(testPublicChatID)
require.NoError(t, err)
require.Equal(t, "", messageID)
err = p.SaveMessages([]*common.Message{
{
ID: "1",
LocalChatID: testPublicChatID,
ChatMessage: protobuf.ChatMessage{
Clock: 1,
Text: "some-text"},
From: testPK,
Seen: true,
},
{
ID: "2",
LocalChatID: testPublicChatID,
ChatMessage: protobuf.ChatMessage{
Clock: 2,
Text: "some-text"},
From: testPK,
Seen: false,
},
{
ID: "3",
LocalChatID: testPublicChatID,
ChatMessage: protobuf.ChatMessage{
Clock: 3,
Text: "some-text"},
From: testPK,
Seen: false,
},
})
require.NoError(t, err)
messageID, err = p.FirstUnseenMessageID(testPublicChatID)
require.NoError(t, err)
require.Equal(t, "2", messageID)
}
func TestLatestMessageByChatID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
var ids []string
for i := 0; i < 10; i++ {
id := strconv.Itoa(i)
err := insertMinimalMessage(p, id)
require.NoError(t, err)
ids = append(ids, id)
}
id := strconv.Itoa(10)
err = insertMinimalDeletedMessage(p, id)
require.NoError(t, err)
ids = append(ids, id)
id = strconv.Itoa(11)
err = insertMinimalDeletedForMeMessage(p, id)
require.NoError(t, err)
ids = append(ids, id)
m, err := p.LatestMessageByChatID(testPublicChatID)
require.NoError(t, err)
require.Equal(t, m[0].ID, ids[9])
}
func TestOldestMessageWhisperTimestampByChatID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chatID := testPublicChatID
_, hasMessage, err := p.OldestMessageWhisperTimestampByChatID(chatID)
require.NoError(t, err)
require.False(t, hasMessage)
var messages []*common.Message
for i := 0; i < 10; i++ {
messages = append(messages, &common.Message{
ID: strconv.Itoa(i),
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
},
WhisperTimestamp: uint64(i + 10),
From: testPK,
})
}
err = p.SaveMessages(messages)
require.NoError(t, err)
timestamp, hasMessage, err := p.OldestMessageWhisperTimestampByChatID(chatID)
require.NoError(t, err)
require.True(t, hasMessage)
require.Equal(t, uint64(10), timestamp)
}
func TestPinMessageByChatID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := sqlitePersistence{db: db}
chatID := "chat-with-pinned-messages"
messagesCount := 1000
pageSize := 5
pinnedMessagesCount := 0
var messages []*common.Message
var pinMessages []*common.PinMessage
for i := 0; i < messagesCount; i++ {
messages = append(messages, &common.Message{
ID: strconv.Itoa(i),
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
},
From: testPK,
})
// Pin this message
if i%100 == 0 {
from := testPK
if i == 100 {
from = "them"
}
pinMessage := &common.PinMessage{
ID: strconv.Itoa(i),
LocalChatID: chatID,
From: from,
}
pinMessage.MessageId = strconv.Itoa(i)
pinMessage.Clock = 111
pinMessage.Pinned = true
pinMessages = append(pinMessages, pinMessage)
pinnedMessagesCount++
if i%200 == 0 {
// unpin a message
unpinMessage := &common.PinMessage{
ID: strconv.Itoa(i),
LocalChatID: chatID,
From: testPK,
}
pinMessage.MessageId = strconv.Itoa(i)
unpinMessage.Clock = 333
unpinMessage.Pinned = false
pinMessages = append(pinMessages, unpinMessage)
pinnedMessagesCount--
// pinned before the unpin
pinMessage2 := &common.PinMessage{
ID: strconv.Itoa(i),
LocalChatID: chatID,
From: testPK,
}
pinMessage2.MessageId = strconv.Itoa(i)
pinMessage2.Clock = 222
pinMessage2.Pinned = true
pinMessages = append(pinMessages, pinMessage2)
}
}
// Add some other chats.
if i%5 == 0 {
messages = append(messages, &common.Message{
ID: strconv.Itoa(messagesCount + i),
LocalChatID: "chat-without-pinned-messages",
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
},
From: testPK,
})
}
}
err = p.SaveMessages(messages)
require.NoError(t, err)
err = p.SavePinMessages(pinMessages)
require.NoError(t, err)
var (
result []*common.PinnedMessage
cursor string
iter int
)
for {
var (
items []*common.PinnedMessage
err error
)
items, cursor, err = p.PinnedMessageByChatID(chatID, cursor, pageSize)
require.NoError(t, err)
result = append(result, items...)
iter++
if len(cursor) == 0 || iter > messagesCount {
break
}
}
require.Equal(t, "", cursor) // for loop should exit because of cursor being empty
require.EqualValues(t, pinnedMessagesCount, len(result))
require.EqualValues(t, math.Ceil(float64(pinnedMessagesCount)/float64(pageSize)), iter)
require.True(
t,
// Verify descending order.
sort.SliceIsSorted(result, func(i, j int) bool {
return result[i].Message.Clock > result[j].Message.Clock
}),
)
require.Equal(t, "them", result[len(result)-1].PinnedBy)
for i := 0; i < len(result)-1; i++ {
require.Equal(t, testPK, result[i].PinnedBy)
}
}
func TestMessageReplies(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chatID := testPublicChatID
message1 := &common.Message{
ID: "id-1",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Text: "content-1",
Clock: uint64(1),
},
From: "1",
}
message2 := &common.Message{
ID: "id-2",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Text: "content-2",
Clock: uint64(2),
ResponseTo: "id-1",
},
From: "2",
}
message3 := &common.Message{
ID: "id-3",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Text: "content-3",
Clock: uint64(3),
ResponseTo: "non-existing",
},
From: "3",
}
// Message that is deleted
message4 := &common.Message{
ID: "id-4",
LocalChatID: chatID,
Deleted: true,
ChatMessage: protobuf.ChatMessage{
Text: "content-4",
Clock: uint64(4),
},
From: "2",
}
// Message replied to a deleted message. It will not have QuotedMessage info
message5 := &common.Message{
ID: "id-5",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Text: "content-4",
Clock: uint64(5),
ResponseTo: "id-4",
},
From: "3",
}
// messages := []*common.Message{message1, message2, message3}
messages := []*common.Message{message1, message2, message3, message4, message5}
err = p.SaveMessages(messages)
require.NoError(t, err)
retrievedMessages, _, err := p.MessageByChatID(chatID, "", 10)
require.NoError(t, err)
require.Equal(t, "non-existing", retrievedMessages[2].ResponseTo)
require.Nil(t, retrievedMessages[2].QuotedMessage)
require.Equal(t, "id-1", retrievedMessages[3].ResponseTo)
require.Equal(t, &common.QuotedMessage{ID: "id-1", From: "1", Text: "content-1"}, retrievedMessages[3].QuotedMessage)
require.Equal(t, "", retrievedMessages[4].ResponseTo)
require.Nil(t, retrievedMessages[4].QuotedMessage)
// We have a ResponseTo, but no QuotedMessage only gives the ID and Deleted
require.Equal(t, "id-4", retrievedMessages[0].ResponseTo)
require.Equal(t, &common.QuotedMessage{ID: "id-4", Deleted: true}, retrievedMessages[0].QuotedMessage)
}
func TestMessageByChatIDWithTheSameClocks(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chatID := testPublicChatID
clockValues := []uint64{10, 10, 9, 9, 9, 11, 12, 11, 100000, 6, 4, 5, 5, 5, 5}
count := len(clockValues)
pageSize := 2
var messages []*common.Message
for i, clock := range clockValues {
messages = append(messages, &common.Message{
ID: strconv.Itoa(i),
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Clock: clock,
},
From: testPK,
})
}
err = p.SaveMessages(messages)
require.NoError(t, err)
var (
result []*common.Message
cursor string
iter int
)
for {
var (
items []*common.Message
err error
)
items, cursor, err = p.MessageByChatID(chatID, cursor, pageSize)
require.NoError(t, err)
result = append(result, items...)
iter++
if cursor == "" || iter > count {
break
}
}
require.Empty(t, cursor) // for loop should exit because of cursor being empty
require.Len(t, result, count)
// Verify the order.
expectedClocks := make([]uint64, len(clockValues))
copy(expectedClocks, clockValues)
sort.Slice(expectedClocks, func(i, j int) bool {
return expectedClocks[i] > expectedClocks[j]
})
resultClocks := make([]uint64, 0, len(clockValues))
for _, m := range result {
resultClocks = append(resultClocks, m.Clock)
}
require.EqualValues(t, expectedClocks, resultClocks)
}
func TestDeleteMessageByID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "1"
err = insertMinimalMessage(p, id)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.Equal(t, id, m.ID)
err = p.DeleteMessage(m.ID)
require.NoError(t, err)
_, err = p.MessageByID(id)
require.EqualError(t, err, "record not found")
}
func TestDeleteMessagesByChatID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
err = insertMinimalMessage(p, "1")
require.NoError(t, err)
err = insertMinimalMessage(p, "2")
require.NoError(t, err)
m, _, err := p.MessageByChatID(testPublicChatID, "", 10)
require.NoError(t, err)
require.Equal(t, 2, len(m))
err = p.DeleteMessagesByChatID(testPublicChatID)
require.NoError(t, err)
m, _, err = p.MessageByChatID(testPublicChatID, "", 10)
require.NoError(t, err)
require.Equal(t, 0, len(m))
}
func TestMarkMessageSeen(t *testing.T) {
chatID := "test-chat"
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "1"
err = insertMinimalMessage(p, id)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.False(t, m.Seen)
count, countWithMention, err := p.MarkMessagesSeen(chatID, []string{m.ID})
require.NoError(t, err)
require.Equal(t, uint64(1), count)
require.Equal(t, uint64(0), countWithMention)
m, err = p.MessageByID(id)
require.NoError(t, err)
require.True(t, m.Seen)
}
func TestUpdateMessageOutgoingStatus(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "1"
err = insertMinimalMessage(p, id)
require.NoError(t, err)
err = p.UpdateMessageOutgoingStatus(id, "new-status")
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
require.Equal(t, "new-status", m.OutgoingStatus)
}
func TestMessagesIDsByType(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
ids, err := p.RawMessagesIDsByType(protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
require.NoError(t, err)
require.Empty(t, ids)
err = p.SaveRawMessage(minimalRawMessage("chat-message-id", protobuf.ApplicationMetadataMessage_CHAT_MESSAGE))
require.NoError(t, err)
ids, err = p.RawMessagesIDsByType(protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
require.NoError(t, err)
require.Equal(t, 1, len(ids))
require.Equal(t, "chat-message-id", ids[0])
ids, err = p.RawMessagesIDsByType(protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
require.NoError(t, err)
require.Empty(t, ids)
err = p.SaveRawMessage(minimalRawMessage("emoji-message-id", protobuf.ApplicationMetadataMessage_EMOJI_REACTION))
require.NoError(t, err)
ids, err = p.RawMessagesIDsByType(protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
require.NoError(t, err)
require.Equal(t, 1, len(ids))
require.Equal(t, "emoji-message-id", ids[0])
}
func TestExpiredMessagesIDs(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
ids, err := p.ExpiredMessagesIDs(messageResendMaxCount)
require.NoError(t, err)
require.Empty(t, ids)
//save expired emoji message
rawEmojiReaction := minimalRawMessage("emoji-message-id", protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
rawEmojiReaction.Sent = false
err = p.SaveRawMessage(rawEmojiReaction)
require.NoError(t, err)
//make sure it appered in expired emoji reactions list
ids, err = p.ExpiredMessagesIDs(messageResendMaxCount)
require.NoError(t, err)
require.Equal(t, 1, len(ids))
//save non-expired emoji reaction
rawEmojiReaction2 := minimalRawMessage("emoji-message-id2", protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
rawEmojiReaction2.Sent = true
err = p.SaveRawMessage(rawEmojiReaction2)
require.NoError(t, err)
//make sure it didn't appear in expired emoji reactions list
ids, err = p.ExpiredMessagesIDs(messageResendMaxCount)
require.NoError(t, err)
require.Equal(t, 1, len(ids))
}
func TestDontOverwriteSentStatus(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
//save rawMessage
rawMessage := minimalRawMessage("chat-message-id", protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
rawMessage.Sent = true
err = p.SaveRawMessage(rawMessage)
require.NoError(t, err)
rawMessage.Sent = false
rawMessage.ResendAutomatically = true
err = p.SaveRawMessage(rawMessage)
require.NoError(t, err)
m, err := p.RawMessageByID(rawMessage.ID)
require.NoError(t, err)
require.True(t, m.Sent)
require.True(t, m.ResendAutomatically)
}
func TestPersistenceEmojiReactions(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
// reverse order as we use DESC
id1 := "1"
id2 := "2"
id3 := "3"
from1 := "from-1"
from2 := "from-2"
from3 := "from-3"
chatID := testPublicChatID
err = insertMinimalMessage(p, id1)
require.NoError(t, err)
err = insertMinimalMessage(p, id2)
require.NoError(t, err)
err = insertMinimalMessage(p, id3)
require.NoError(t, err)
// Insert normal emoji reaction
require.NoError(t, p.SaveEmojiReaction(&EmojiReaction{
EmojiReaction: protobuf.EmojiReaction{
Clock: 1,
MessageId: id3,
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
},
LocalChatID: chatID,
From: from1,
}))
// Insert retracted emoji reaction
require.NoError(t, p.SaveEmojiReaction(&EmojiReaction{
EmojiReaction: protobuf.EmojiReaction{
Clock: 1,
MessageId: id3,
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
Retracted: true,
},
LocalChatID: chatID,
From: from2,
}))
// Insert retracted emoji reaction out of pagination
require.NoError(t, p.SaveEmojiReaction(&EmojiReaction{
EmojiReaction: protobuf.EmojiReaction{
Clock: 1,
MessageId: id1,
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
},
LocalChatID: chatID,
From: from2,
}))
// Insert retracted emoji reaction out of pagination
require.NoError(t, p.SaveEmojiReaction(&EmojiReaction{
EmojiReaction: protobuf.EmojiReaction{
Clock: 1,
MessageId: id1,
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
},
LocalChatID: chatID,
From: from3,
}))
// Wrong local chat id
require.NoError(t, p.SaveEmojiReaction(&EmojiReaction{
EmojiReaction: protobuf.EmojiReaction{
Clock: 1,
MessageId: id1,
ChatId: chatID,
Type: protobuf.EmojiReaction_LOVE,
},
LocalChatID: "wrong-chat-id",
From: from3,
}))
reactions, err := p.EmojiReactionsByChatID(chatID, "", 1)
require.NoError(t, err)
require.Len(t, reactions, 1)
require.Equal(t, id3, reactions[0].MessageId)
// Try with a cursor
_, cursor, err := p.MessageByChatID(chatID, "", 1)
require.NoError(t, err)
reactions, err = p.EmojiReactionsByChatID(chatID, cursor, 2)
require.NoError(t, err)
require.Len(t, reactions, 2)
require.Equal(t, id1, reactions[0].MessageId)
require.Equal(t, id1, reactions[1].MessageId)
}
func openTestDB() (*sql.DB, error) {
dbPath, err := ioutil.TempFile("", "status-go-test-db-")
if err != nil {
return nil, err
}
return sqlite.Open(dbPath.Name(), "", sqlite.ReducedKDFIterationsNumber)
}
func insertMinimalMessage(p *sqlitePersistence, id string) error {
return p.SaveMessages([]*common.Message{{
ID: id,
LocalChatID: testPublicChatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
}})
}
func insertMinimalDeletedMessage(p *sqlitePersistence, id string) error {
return p.SaveMessages([]*common.Message{{
ID: id,
Deleted: true,
LocalChatID: testPublicChatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
}})
}
func insertMinimalDeletedForMeMessage(p *sqlitePersistence, id string) error {
return p.SaveMessages([]*common.Message{{
ID: id,
DeletedForMe: true,
LocalChatID: testPublicChatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
}})
}
func insertDiscordMessageWithAttachments(p *sqlitePersistence, id string, discordMessageID string) error {
err := insertMinimalDiscordMessage(p, id, discordMessageID)
if err != nil {
return err
}
attachment := &protobuf.DiscordMessageAttachment{
Id: "1",
MessageId: discordMessageID,
Url: "https://does-not-exist.com",
Payload: []byte{1, 2, 3, 4},
}
attachment2 := &protobuf.DiscordMessageAttachment{
Id: "2",
MessageId: discordMessageID,
Url: "https://does-not-exist.com",
Payload: []byte{5, 6, 7, 8},
}
return p.SaveDiscordMessageAttachments([]*protobuf.DiscordMessageAttachment{
attachment,
attachment2,
})
}
func insertMinimalDiscordMessage(p *sqlitePersistence, id string, discordMessageID string) error {
discordMessage := &protobuf.DiscordMessage{
Id: discordMessageID,
Type: "Default",
Timestamp: "123456",
Content: "This is the message",
Author: &protobuf.DiscordMessageAuthor{
Id: "2",
},
Reference: &protobuf.DiscordMessageReference{},
}
err := p.SaveDiscordMessage(discordMessage)
if err != nil {
return err
}
return p.SaveMessages([]*common.Message{{
ID: id,
LocalChatID: testPublicChatID,
From: testPK,
ChatMessage: protobuf.ChatMessage{
Text: "some-text",
ContentType: protobuf.ChatMessage_DISCORD_MESSAGE,
ChatId: testPublicChatID,
Payload: &protobuf.ChatMessage_DiscordMessage{
DiscordMessage: discordMessage,
},
},
}})
}
func minimalRawMessage(id string, messageType protobuf.ApplicationMetadataMessage_Type) *common.RawMessage {
return &common.RawMessage{
ID: id,
LocalChatID: "test-chat",
MessageType: messageType,
}
}
// Regression test making sure that if audio_duration_ms is null, no error is thrown
func TestMessagesAudioDurationMsNull(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
id := "message-id-1"
err = insertMinimalMessage(p, id)
require.NoError(t, err)
_, err = p.db.Exec("UPDATE user_messages SET audio_duration_ms = NULL")
require.NoError(t, err)
m, err := p.MessagesByIDs([]string{id})
require.NoError(t, err)
require.Len(t, m, 1)
m, _, err = p.MessageByChatID(testPublicChatID, "", 10)
require.NoError(t, err)
require.Len(t, m, 1)
}
func TestSaveChat(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chat := CreatePublicChat("test-chat", &testTimeSource{})
chat.LastMessage = &common.Message{}
err = p.SaveChat(*chat)
require.NoError(t, err)
retrievedChat, err := p.Chat(chat.ID)
require.NoError(t, err)
require.Equal(t, chat, retrievedChat)
}
func TestSaveMentions(t *testing.T) {
chatID := testPublicChatID
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
key, err := crypto.GenerateKey()
require.NoError(t, err)
pkString := types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey))
message := common.Message{
ID: "1",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
Mentions: []string{pkString},
}
err = p.SaveMessages([]*common.Message{&message})
require.NoError(t, err)
retrievedMessages, _, err := p.MessageByChatID(chatID, "", 10)
require.NoError(t, err)
require.Len(t, retrievedMessages, 1)
require.Len(t, retrievedMessages[0].Mentions, 1)
require.Equal(t, retrievedMessages[0].Mentions, message.Mentions)
}
func TestSqlitePersistence_GetWhenChatIdentityLastPublished(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chatID := "0xabcd1234"
hash := []byte{0x1}
now := time.Now().Unix()
err = p.SaveWhenChatIdentityLastPublished(chatID, hash)
require.NoError(t, err)
ts, actualHash, err := p.GetWhenChatIdentityLastPublished(chatID)
require.NoError(t, err)
// Check that the save happened in the last 2 seconds
diff := ts - now
require.LessOrEqual(t, diff, int64(2))
require.True(t, bytes.Equal(hash, actualHash))
// Require unsaved values to be zero
ts2, actualHash2, err := p.GetWhenChatIdentityLastPublished("0xdeadbeef")
require.NoError(t, err)
require.Exactly(t, int64(0), ts2)
require.Nil(t, actualHash2)
}
func TestSaveContactChatIdentity(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
key, err := crypto.GenerateKey()
require.NoError(t, err)
contactID := types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey))
err = p.SaveContact(&Contact{ID: contactID}, nil)
require.NoError(t, err)
jpegType := []byte{0xff, 0xd8, 0xff, 0x1}
identityImages := make(map[string]*protobuf.IdentityImage)
identityImages["large"] = &protobuf.IdentityImage{
Payload: jpegType,
SourceType: protobuf.IdentityImage_RAW_PAYLOAD,
ImageType: protobuf.ImageType_PNG,
}
identityImages["small"] = &protobuf.IdentityImage{
Payload: jpegType,
SourceType: protobuf.IdentityImage_RAW_PAYLOAD,
ImageType: protobuf.ImageType_PNG,
}
toArrayOfPointers := func(array []protobuf.SocialLink) (result []*protobuf.SocialLink) {
result = make([]*protobuf.SocialLink, len(array))
for i := range array {
result[i] = &array[i]
}
return
}
chatIdentity := &protobuf.ChatIdentity{
Clock: 1,
Images: identityImages,
SocialLinks: toArrayOfPointers([]protobuf.SocialLink{
{
Text: "Personal Site",
Url: "status.im",
},
{
Text: "Twitter",
Url: "Status_ico",
},
}),
}
clockUpdated, imagesUpdated, err := p.SaveContactChatIdentity(contactID, chatIdentity)
require.NoError(t, err)
require.True(t, clockUpdated)
require.True(t, imagesUpdated)
// Save again same clock and data
clockUpdated, imagesUpdated, err = p.SaveContactChatIdentity(contactID, chatIdentity)
require.NoError(t, err)
require.False(t, clockUpdated)
require.False(t, imagesUpdated)
// Save again newer clock and no images
chatIdentity.Clock = 2
chatIdentity.Images = make(map[string]*protobuf.IdentityImage)
clockUpdated, imagesUpdated, err = p.SaveContactChatIdentity(contactID, chatIdentity)
require.NoError(t, err)
require.True(t, clockUpdated)
require.False(t, imagesUpdated)
contacts, err := p.Contacts()
require.NoError(t, err)
require.Len(t, contacts, 1)
require.Len(t, contacts[0].Images, 2)
require.Len(t, contacts[0].SocialLinks, 2)
require.Equal(t, "Personal Site", contacts[0].SocialLinks[0].Text)
require.Equal(t, "status.im", contacts[0].SocialLinks[0].URL)
require.Equal(t, "Twitter", contacts[0].SocialLinks[1].Text)
require.Equal(t, "Status_ico", contacts[0].SocialLinks[1].URL)
}
func TestSaveLinks(t *testing.T) {
chatID := testPublicChatID
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
require.NoError(t, err)
message := common.Message{
ID: "1",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
Links: []string{"https://github.com/status-im/status-mobile"},
}
err = p.SaveMessages([]*common.Message{&message})
require.NoError(t, err)
retrievedMessages, _, err := p.MessageByChatID(chatID, "", 10)
require.NoError(t, err)
require.Len(t, retrievedMessages, 1)
require.Len(t, retrievedMessages[0].Links, 1)
require.Equal(t, retrievedMessages[0].Links, message.Links)
}
func TestSaveWithUnfurledLinks(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
require.NoError(t, err)
chatID := testPublicChatID
message := common.Message{
ID: "1",
LocalChatID: chatID,
From: testPK,
ChatMessage: protobuf.ChatMessage{
Text: "some-text",
UnfurledLinks: []*protobuf.UnfurledLink{
{
Url: "https://github.com",
Title: "Build software better, together",
Description: "GitHub is where people build software.",
ThumbnailPayload: []byte("abc"),
},
{
Url: "https://www.youtube.com/watch?v=mzOyYtfXkb0",
Title: "Status Town Hall #67 - 12 October 2020",
Description: "",
ThumbnailPayload: []byte("def"),
},
},
},
}
err = p.SaveMessages([]*common.Message{&message})
require.NoError(t, err)
mgs, _, err := p.MessageByChatID(chatID, "", 10)
require.NoError(t, err)
require.Len(t, mgs, 1)
require.Len(t, mgs[0].UnfurledLinks, 2)
require.Equal(t, mgs[0].UnfurledLinks, message.UnfurledLinks)
}
func TestHideMessage(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
chatID := testPublicChatID
message := &common.Message{
ID: "id-1",
LocalChatID: chatID,
ChatMessage: protobuf.ChatMessage{
Text: "content-1",
Clock: uint64(1),
},
From: "1",
}
messages := []*common.Message{message}
err = p.SaveMessages(messages)
require.NoError(t, err)
err = p.HideMessage(message.ID)
require.NoError(t, err)
var actualHidden, actualSeen bool
err = p.db.QueryRow("SELECT hide, seen FROM user_messages WHERE id = ?", message.ID).Scan(&actualHidden, &actualSeen)
require.NoError(t, err)
require.True(t, actualHidden)
require.True(t, actualSeen)
}
func TestDeactivatePublicChat(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
publicChatID := "public-chat-id"
var currentClockValue uint64 = 10
timesource := &testTimeSource{}
lastMessage := common.Message{
ID: "0x01",
LocalChatID: publicChatID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
}
lastMessage.Clock = 20
require.NoError(t, p.SaveMessages([]*common.Message{&lastMessage}))
publicChat := CreatePublicChat(publicChatID, timesource)
publicChat.LastMessage = &lastMessage
publicChat.UnviewedMessagesCount = 1
err = p.DeactivateChat(publicChat, currentClockValue, true)
// It does not set deleted at for a public chat
require.NoError(t, err)
require.Equal(t, uint64(0), publicChat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, publicChat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), publicChat.UnviewedMessagesCount)
// It sets active as false
require.False(t, publicChat.Active)
// It deletes messages
messages, _, err := p.MessageByChatID(publicChatID, "", 10)
require.NoError(t, err)
require.Len(t, messages, 0)
// Reload chat to make sure it has been save
dbChat, err := p.Chat(publicChatID)
require.NoError(t, err)
require.NotNil(t, dbChat)
// Same checks on the chat pulled from the db
// It does not set deleted at for a public chat
require.NoError(t, err)
require.Equal(t, uint64(0), dbChat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, dbChat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), dbChat.UnviewedMessagesCount)
// It sets active as false
require.False(t, dbChat.Active)
}
func TestDeactivateOneToOneChat(t *testing.T) {
key, err := crypto.GenerateKey()
require.NoError(t, err)
pkString := types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey))
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
var currentClockValue uint64 = 10
timesource := &testTimeSource{}
chat := CreateOneToOneChat(pkString, &key.PublicKey, timesource)
lastMessage := common.Message{
ID: "0x01",
LocalChatID: chat.ID,
ChatMessage: protobuf.ChatMessage{Text: "some-text"},
From: testPK,
}
lastMessage.Clock = 20
require.NoError(t, p.SaveMessages([]*common.Message{&lastMessage}))
chat.LastMessage = &lastMessage
chat.UnviewedMessagesCount = 1
err = p.DeactivateChat(chat, currentClockValue, true)
// It does set deleted at for a public chat
require.NoError(t, err)
require.NotEqual(t, uint64(0), chat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, chat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), chat.UnviewedMessagesCount)
// It sets active as false
require.False(t, chat.Active)
// It deletes messages
messages, _, err := p.MessageByChatID(chat.ID, "", 10)
require.NoError(t, err)
require.Len(t, messages, 0)
// Reload chat to make sure it has been save
dbChat, err := p.Chat(chat.ID)
require.NoError(t, err)
require.NotNil(t, dbChat)
// Same checks on the chat pulled from the db
// It does set deleted at for a public chat
require.NoError(t, err)
require.NotEqual(t, uint64(0), dbChat.DeletedAtClockValue)
// It sets the lastMessage to nil
require.Nil(t, dbChat.LastMessage)
// It sets unviewed messages count
require.Equal(t, uint(0), dbChat.UnviewedMessagesCount)
// It sets active as false
require.False(t, dbChat.Active)
}
func TestConfirmations(t *testing.T) {
dataSyncID1 := []byte("datsync-id-1")
dataSyncID2 := []byte("datsync-id-2")
dataSyncID3 := []byte("datsync-id-3")
dataSyncID4 := []byte("datsync-id-3")
messageID1 := []byte("message-id-1")
messageID2 := []byte("message-id-2")
publicKey1 := []byte("pk-1")
publicKey2 := []byte("pk-2")
publicKey3 := []byte("pk-3")
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
confirmation1 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID1,
MessageID: messageID1,
PublicKey: publicKey1,
}
// Same datasyncID and same messageID, different pubkey
confirmation2 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID2,
MessageID: messageID1,
PublicKey: publicKey2,
}
// Different datasyncID and same messageID, different pubkey
confirmation3 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID3,
MessageID: messageID1,
PublicKey: publicKey3,
}
// Same dataSyncID, different messageID
confirmation4 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID4,
MessageID: messageID2,
PublicKey: publicKey1,
}
require.NoError(t, p.InsertPendingConfirmation(confirmation1))
require.NoError(t, p.InsertPendingConfirmation(confirmation2))
require.NoError(t, p.InsertPendingConfirmation(confirmation3))
require.NoError(t, p.InsertPendingConfirmation(confirmation4))
// We confirm the first datasync message, no confirmations
messageID, err := p.MarkAsConfirmed(dataSyncID1, false)
require.NoError(t, err)
require.Nil(t, messageID)
// We confirm the second datasync message, no confirmations
messageID, err = p.MarkAsConfirmed(dataSyncID2, false)
require.NoError(t, err)
require.Nil(t, messageID)
// We confirm the third datasync message, messageID1 should be confirmed
messageID, err = p.MarkAsConfirmed(dataSyncID3, false)
require.NoError(t, err)
require.Equal(t, messageID, types.HexBytes(messageID1))
}
func TestConfirmationsAtLeastOne(t *testing.T) {
dataSyncID1 := []byte("datsync-id-1")
dataSyncID2 := []byte("datsync-id-2")
dataSyncID3 := []byte("datsync-id-3")
messageID1 := []byte("message-id-1")
publicKey1 := []byte("pk-1")
publicKey2 := []byte("pk-2")
publicKey3 := []byte("pk-3")
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
confirmation1 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID1,
MessageID: messageID1,
PublicKey: publicKey1,
}
// Same datasyncID and same messageID, different pubkey
confirmation2 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID2,
MessageID: messageID1,
PublicKey: publicKey2,
}
// Different datasyncID and same messageID, different pubkey
confirmation3 := &common.RawMessageConfirmation{
DataSyncID: dataSyncID3,
MessageID: messageID1,
PublicKey: publicKey3,
}
require.NoError(t, p.InsertPendingConfirmation(confirmation1))
require.NoError(t, p.InsertPendingConfirmation(confirmation2))
require.NoError(t, p.InsertPendingConfirmation(confirmation3))
// We confirm the first datasync message, messageID1 and 3 should be confirmed
messageID, err := p.MarkAsConfirmed(dataSyncID1, true)
require.NoError(t, err)
require.NotNil(t, messageID)
require.Equal(t, types.HexBytes(messageID1), messageID)
}
func TestSaveCommunityChat(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
identity := &protobuf.ChatIdentity{
DisplayName: "community-chat-name",
Description: "community-chat-name-description",
FirstMessageTimestamp: 1,
}
permissions := &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
}
communityChat := &protobuf.CommunityChat{
Identity: identity,
Permissions: permissions,
}
chat := CreateCommunityChat("test-or-gid", "test-chat-id", communityChat, &testTimeSource{})
chat.LastMessage = &common.Message{}
err = p.SaveChat(*chat)
require.NoError(t, err)
retrievedChat, err := p.Chat(chat.ID)
require.NoError(t, err)
require.Equal(t, chat, retrievedChat)
}
func TestSaveDiscordMessageAuthor(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
testAuthor := &protobuf.DiscordMessageAuthor{
Id: "1",
Name: "Testuser",
Discriminator: "1234",
Nickname: "User",
AvatarUrl: "http://example.com/profile.jpg",
AvatarImagePayload: []byte{1, 2, 3},
}
require.NoError(t, p.SaveDiscordMessageAuthor(testAuthor))
exists, err := p.HasDiscordMessageAuthor("1")
require.NoError(t, err)
require.True(t, exists)
author, err := p.GetDiscordMessageAuthorByID("1")
require.NoError(t, err)
require.Equal(t, author.Id, testAuthor.Id)
require.Equal(t, author.Name, testAuthor.Name)
}
func TestGetDiscordMessageAuthorImagePayloadByID(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
testAuthor := &protobuf.DiscordMessageAuthor{
Id: "1",
Name: "Testuser",
Discriminator: "1234",
Nickname: "User",
AvatarUrl: "http://example.com/profile.jpg",
AvatarImagePayload: []byte{1, 2, 3},
}
require.NoError(t, p.SaveDiscordMessageAuthor(testAuthor))
payload, err := p.GetDiscordMessageAuthorImagePayloadByID("1")
require.NoError(t, err)
require.Equal(t, testAuthor.AvatarImagePayload, payload)
}
func TestSaveDiscordMessage(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
require.NoError(t, p.SaveDiscordMessage(&protobuf.DiscordMessage{
Id: "1",
Type: "Default",
Timestamp: "123456",
Content: "This is the message",
Author: &protobuf.DiscordMessageAuthor{
Id: "2",
},
Reference: &protobuf.DiscordMessageReference{},
}))
require.NoError(t, err)
}
func TestSaveDiscordMessages(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
for i := 0; i < 10; i++ {
id := strconv.Itoa(i)
err := insertMinimalDiscordMessage(p, id, id)
require.NoError(t, err)
m, err := p.MessageByID(id)
require.NoError(t, err)
dm := m.GetDiscordMessage()
require.NotNil(t, dm)
require.EqualValues(t, id, dm.Id)
require.EqualValues(t, "2", dm.Author.Id)
}
}
func TestUpdateDiscordMessageAuthorImage(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
require.NoError(t, p.SaveDiscordMessageAuthor(&protobuf.DiscordMessageAuthor{
Id: "1",
Name: "Testuser",
Discriminator: "1234",
Nickname: "User",
AvatarUrl: "http://example.com/profile.jpg",
}))
exists, err := p.HasDiscordMessageAuthor("1")
require.NoError(t, err)
require.True(t, exists)
err = p.UpdateDiscordMessageAuthorImage("1", []byte{0, 1, 2, 3})
require.NoError(t, err)
payload, err := p.GetDiscordMessageAuthorImagePayloadByID("1")
require.NoError(t, err)
require.Equal(t, []byte{0, 1, 2, 3}, payload)
}
func TestSaveHashRatchetMessage(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
groupID1 := []byte("group-id-1")
groupID2 := []byte("group-id-2")
var keyID uint32 = 3
message1 := &types.Message{
Hash: []byte{1},
Sig: []byte{2},
TTL: 1,
Timestamp: 2,
Payload: []byte{3},
}
require.NoError(t, p.SaveHashRatchetMessage(groupID1, keyID, message1))
message2 := &types.Message{
Hash: []byte{2},
Sig: []byte{2},
TTL: 1,
Topic: types.BytesToTopic([]byte{5}),
Timestamp: 2,
Payload: []byte{3},
Dst: []byte{4},
P2P: true,
}
require.NoError(t, p.SaveHashRatchetMessage(groupID2, keyID, message2))
fetchedMessages, err := p.GetHashRatchetMessages(groupID1, keyID)
require.NoError(t, err)
require.NotNil(t, fetchedMessages)
require.Len(t, fetchedMessages, 1)
require.Equal(t, fetchedMessages[0], message1)
}
func TestCountActiveChattersInCommunity(t *testing.T) {
db, err := openTestDB()
require.NoError(t, err)
p := newSQLitePersistence(db)
channel1 := Chat{
ID: "channel1",
Name: "channel1",
CommunityID: "testCommunity",
}
channel2 := Chat{
ID: "channel2",
Name: "channel2",
CommunityID: "testCommunity",
}
require.NoError(t, p.SaveChat(channel1))
require.NoError(t, p.SaveChat(channel2))
fillChatWithMessages := func(chat *Chat, offset int) {
count := 5
var messages []*common.Message
for i := 0; i < count; i++ {
messages = append(messages, &common.Message{
ID: fmt.Sprintf("%smsg%d", chat.Name, i),
LocalChatID: chat.ID,
ChatMessage: protobuf.ChatMessage{
Clock: uint64(i),
Timestamp: uint64(i + offset),
},
From: fmt.Sprintf("user%d", i),
})
}
require.NoError(t, p.SaveMessages(messages))
}
// timestamp/user/msgID
// channel1: 0/user0/channel1msg0 1/user1/channel1msg1 2/user2/channel1msg2 3/user3/channel1msg3 4/user4/channel1msg4
// channel2: 3/user0/channel2msg0 4/user1/channel2msg1 5/user2/channel2msg2 6/user3/channel2msg3 7/user4/channel2msg4
fillChatWithMessages(&channel1, 0)
fillChatWithMessages(&channel2, 3)
checker := func(activeAfterTimestamp int64, expected uint) {
result, err := p.CountActiveChattersInCommunity("testCommunity", activeAfterTimestamp)
require.NoError(t, err)
require.Equal(t, expected, result)
}
checker(0, 5)
checker(3, 5)
checker(4, 4)
checker(5, 3)
checker(6, 2)
checker(7, 1)
checker(8, 0)
}