status-go/protocol/communities/manager_test.go
Pascal Precht 9b04633bb7 feat(communities): introduce publish flag in community manager APIs
This is so that we can control whether we want to publish the community
when it, or it's categories and channels, are created.

This is needed for the discord import so that we can create communities,
channels and categories without publishing the community and have it
show up in UIs too early.
2022-08-25 12:59:30 +02:00

579 lines
18 KiB
Go

package communities
import (
"bytes"
"image"
"image/png"
"io/ioutil"
"os"
"testing"
"time"
"github.com/status-im/status-go/appdatabase"
"github.com/status-im/status-go/eth-node/types"
userimages "github.com/status-im/status-go/images"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/transport"
"github.com/golang/protobuf/proto"
_ "github.com/mutecomm/go-sqlcipher" // require go-sqlcipher that overrides default implementation
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/sqlite"
)
func TestManagerSuite(t *testing.T) {
suite.Run(t, new(ManagerSuite))
}
type ManagerSuite struct {
suite.Suite
manager *Manager
}
func (s *ManagerSuite) SetupTest() {
dbPath, err := ioutil.TempFile("", "")
s.NoError(err, "creating temp file for db")
db, err := appdatabase.InitializeDB(dbPath.Name(), "")
s.NoError(err, "creating sqlite db instance")
err = sqlite.Migrate(db)
s.NoError(err, "protocol migrate")
key, err := crypto.GenerateKey()
s.Require().NoError(err)
s.Require().NoError(err)
m, err := NewManager(&key.PublicKey, db, nil, nil, nil, nil)
s.Require().NoError(err)
s.Require().NoError(m.Start())
s.manager = m
}
func (s *ManagerSuite) TestCreateCommunity() {
request := &requests.CreateCommunity{
Name: "status",
Description: "status community description",
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
}
community, err := s.manager.CreateCommunity(request, true)
s.Require().NoError(err)
s.Require().NotNil(community)
communities, err := s.manager.All()
s.Require().NoError(err)
// Consider status default community
s.Require().Len(communities, 2)
actualCommunity := communities[0]
if bytes.Equal(community.ID(), communities[1].ID()) {
actualCommunity = communities[1]
}
s.Require().Equal(community.ID(), actualCommunity.ID())
s.Require().Equal(community.PrivateKey(), actualCommunity.PrivateKey())
s.Require().True(proto.Equal(community.config.CommunityDescription, actualCommunity.config.CommunityDescription))
}
func (s *ManagerSuite) TestCreateCommunity_WithBanner() {
// Generate test image bigger than BannerDim
testImage := image.NewRGBA(image.Rect(0, 0, 20, 10))
tmpTestFilePath := s.T().TempDir() + "/test.png"
file, err := os.Create(tmpTestFilePath)
s.NoError(err)
defer file.Close()
err = png.Encode(file, testImage)
s.Require().NoError(err)
request := &requests.CreateCommunity{
Name: "with_banner",
Description: "community with banner ",
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Banner: userimages.CroppedImage{
ImagePath: tmpTestFilePath,
X: 1,
Y: 1,
Width: 10,
Height: 5,
},
}
community, err := s.manager.CreateCommunity(request, true)
s.Require().NoError(err)
s.Require().NotNil(community)
communities, err := s.manager.All()
s.Require().NoError(err)
// Consider status default community
s.Require().Len(communities, 2)
s.Require().Equal(len(community.config.CommunityDescription.Identity.Images), 1)
testIdentityImage, isMapContainsKey := community.config.CommunityDescription.Identity.Images[userimages.BannerIdentityName]
s.Require().True(isMapContainsKey)
s.Require().Positive(len(testIdentityImage.Payload))
}
func (s *ManagerSuite) TestEditCommunity() {
//create community
createRequest := &requests.CreateCommunity{
Name: "status",
Description: "status community description",
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
}
community, err := s.manager.CreateCommunity(createRequest, true)
s.Require().NoError(err)
s.Require().NotNil(community)
update := &requests.EditCommunity{
CommunityID: community.ID(),
CreateCommunity: requests.CreateCommunity{
Name: "statusEdited",
Description: "status community description edited",
},
}
updatedCommunity, err := s.manager.EditCommunity(update)
s.Require().NoError(err)
s.Require().NotNil(updatedCommunity)
//ensure updated community successfully stored
communities, err := s.manager.All()
s.Require().NoError(err)
// Consider status default community
s.Require().Len(communities, 2)
storedCommunity := communities[0]
if bytes.Equal(community.ID(), communities[1].ID()) {
storedCommunity = communities[1]
}
s.Require().Equal(storedCommunity.ID(), updatedCommunity.ID())
s.Require().Equal(storedCommunity.PrivateKey(), updatedCommunity.PrivateKey())
s.Require().Equal(storedCommunity.config.CommunityDescription.Identity.DisplayName, update.CreateCommunity.Name)
s.Require().Equal(storedCommunity.config.CommunityDescription.Identity.Description, update.CreateCommunity.Description)
}
func (s *ManagerSuite) TestGetAdminCommuniesChatIDs() {
community, _, err := s.buildCommunityWithChat()
s.Require().NoError(err)
s.Require().NotNil(community)
adminChatIDs, err := s.manager.GetAdminCommunitiesChatIDs()
s.Require().NoError(err)
s.Require().Len(adminChatIDs, 1)
}
func (s *ManagerSuite) TestStartAndStopTorrentClient() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
err := s.manager.StartTorrentClient()
s.Require().NoError(err)
s.Require().NotNil(s.manager.torrentClient)
defer s.manager.StopTorrentClient()
_, err = os.Stat(torrentConfig.DataDir)
s.Require().NoError(err)
s.Require().Equal(s.manager.TorrentClientStarted(), true)
}
func (s *ManagerSuite) TestStartHistoryArchiveTasksInterval() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
err := s.manager.StartTorrentClient()
s.Require().NoError(err)
defer s.manager.StopTorrentClient()
community, _, err := s.buildCommunityWithChat()
s.Require().NoError(err)
interval := 10 * time.Second
go s.manager.StartHistoryArchiveTasksInterval(community, interval)
// Due to async exec we need to wait a bit until we check
// the task count.
time.Sleep(5 * time.Second)
s.Require().Len(s.manager.historyArchiveTasks, 1)
// We wait another 5 seconds to ensure the first tick has kicked in
time.Sleep(5 * time.Second)
_, err = os.Stat(s.manager.torrentFile(community.IDString()))
s.Require().Error(err)
s.manager.StopHistoryArchiveTasksInterval(community.ID())
s.manager.historyArchiveTasksWaitGroup.Wait()
s.Require().Len(s.manager.historyArchiveTasks, 0)
}
func (s *ManagerSuite) TestStopHistoryArchiveTasksIntervals() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
err := s.manager.StartTorrentClient()
s.Require().NoError(err)
defer s.manager.StopTorrentClient()
community, _, err := s.buildCommunityWithChat()
s.Require().NoError(err)
interval := 10 * time.Second
go s.manager.StartHistoryArchiveTasksInterval(community, interval)
time.Sleep(2 * time.Second)
s.Require().Len(s.manager.historyArchiveTasks, 1)
s.manager.StopHistoryArchiveTasksIntervals()
s.Require().Len(s.manager.historyArchiveTasks, 0)
}
func (s *ManagerSuite) TestStopTorrentClient_ShouldStopHistoryArchiveTasks() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
err := s.manager.StartTorrentClient()
s.Require().NoError(err)
defer s.manager.StopTorrentClient()
community, _, err := s.buildCommunityWithChat()
s.Require().NoError(err)
interval := 10 * time.Second
go s.manager.StartHistoryArchiveTasksInterval(community, interval)
// Due to async exec we need to wait a bit until we check
// the task count.
time.Sleep(2 * time.Second)
s.Require().Len(s.manager.historyArchiveTasks, 1)
errs := s.manager.StopTorrentClient()
s.Require().Len(errs, 0)
s.Require().Len(s.manager.historyArchiveTasks, 0)
}
func (s *ManagerSuite) TestCreateHistoryArchiveTorrent_WithoutMessages() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
community, chatID, err := s.buildCommunityWithChat()
s.Require().NoError(err)
topic := types.BytesToTopic(transport.ToTopic(chatID))
topics := []types.TopicType{topic}
// Time range of 7 days
startDate := time.Date(2020, 1, 1, 00, 00, 00, 0, time.UTC)
endDate := time.Date(2020, 1, 7, 00, 00, 00, 0, time.UTC)
// Partition of 7 days
partition := 7 * 24 * time.Hour
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
// There are no waku messages in the database so we don't expect
// any archives to be created
_, err = os.Stat(s.manager.archiveDataFile(community.IDString()))
s.Require().Error(err)
_, err = os.Stat(s.manager.archiveIndexFile(community.IDString()))
s.Require().Error(err)
_, err = os.Stat(s.manager.torrentFile(community.IDString()))
s.Require().Error(err)
}
func (s *ManagerSuite) TestCreateHistoryArchiveTorrent_ShouldCreateArchive() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
community, chatID, err := s.buildCommunityWithChat()
s.Require().NoError(err)
topic := types.BytesToTopic(transport.ToTopic(chatID))
topics := []types.TopicType{topic}
// Time range of 7 days
startDate := time.Date(2020, 1, 1, 00, 00, 00, 0, time.UTC)
endDate := time.Date(2020, 1, 7, 00, 00, 00, 0, time.UTC)
// Partition of 7 days, this should create a single archive
partition := 7 * 24 * time.Hour
message1 := buildMessage(startDate.Add(1*time.Hour), topic, []byte{1})
message2 := buildMessage(startDate.Add(2*time.Hour), topic, []byte{2})
// This message is outside of the startDate-endDate range and should not
// be part of the archive
message3 := buildMessage(endDate.Add(2*time.Hour), topic, []byte{3})
err = s.manager.StoreWakuMessage(&message1)
s.Require().NoError(err)
err = s.manager.StoreWakuMessage(&message2)
s.Require().NoError(err)
err = s.manager.StoreWakuMessage(&message3)
s.Require().NoError(err)
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
_, err = os.Stat(s.manager.archiveDataFile(community.IDString()))
s.Require().NoError(err)
_, err = os.Stat(s.manager.archiveIndexFile(community.IDString()))
s.Require().NoError(err)
_, err = os.Stat(s.manager.torrentFile(community.IDString()))
s.Require().NoError(err)
index, err := s.manager.LoadHistoryArchiveIndexFromFile(community.ID())
s.Require().NoError(err)
s.Require().Len(index.Archives, 1)
totalData, err := os.ReadFile(s.manager.archiveDataFile(community.IDString()))
s.Require().NoError(err)
for _, metadata := range index.Archives {
archive := &protobuf.WakuMessageArchive{}
data := totalData[metadata.Offset : metadata.Offset+metadata.Size-metadata.Padding]
err = proto.Unmarshal(data, archive)
s.Require().NoError(err)
s.Require().Len(archive.Messages, 2)
}
}
func (s *ManagerSuite) TestCreateHistoryArchiveTorrent_ShouldCreateMultipleArchives() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
community, chatID, err := s.buildCommunityWithChat()
s.Require().NoError(err)
topic := types.BytesToTopic(transport.ToTopic(chatID))
topics := []types.TopicType{topic}
// Time range of 3 weeks
startDate := time.Date(2020, 1, 1, 00, 00, 00, 0, time.UTC)
endDate := time.Date(2020, 1, 21, 00, 00, 00, 0, time.UTC)
// 7 days partition, this should create three archives
partition := 7 * 24 * time.Hour
message1 := buildMessage(startDate.Add(1*time.Hour), topic, []byte{1})
message2 := buildMessage(startDate.Add(2*time.Hour), topic, []byte{2})
// We expect 2 archives to be created for startDate - endDate of each
// 7 days of data. This message should end up in the second archive
message3 := buildMessage(startDate.Add(8*24*time.Hour), topic, []byte{3})
// This one should end up in the third archive
message4 := buildMessage(startDate.Add(14*24*time.Hour), topic, []byte{4})
err = s.manager.StoreWakuMessage(&message1)
s.Require().NoError(err)
err = s.manager.StoreWakuMessage(&message2)
s.Require().NoError(err)
err = s.manager.StoreWakuMessage(&message3)
s.Require().NoError(err)
err = s.manager.StoreWakuMessage(&message4)
s.Require().NoError(err)
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
index, err := s.manager.LoadHistoryArchiveIndexFromFile(community.ID())
s.Require().NoError(err)
s.Require().Len(index.Archives, 3)
totalData, err := os.ReadFile(s.manager.archiveDataFile(community.IDString()))
s.Require().NoError(err)
// First archive has 2 messages
// Second archive has 1 message
// Third archive has 1 message
fromMap := map[uint64]int{
uint64(startDate.Unix()): 2,
uint64(startDate.Add(partition).Unix()): 1,
uint64(startDate.Add(partition * 2).Unix()): 1,
}
for _, metadata := range index.Archives {
archive := &protobuf.WakuMessageArchive{}
data := totalData[metadata.Offset : metadata.Offset+metadata.Size-metadata.Padding]
err = proto.Unmarshal(data, archive)
s.Require().NoError(err)
s.Require().Len(archive.Messages, fromMap[metadata.Metadata.From])
}
}
func (s *ManagerSuite) TestCreateHistoryArchiveTorrent_ShouldAppendArchives() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
community, chatID, err := s.buildCommunityWithChat()
s.Require().NoError(err)
topic := types.BytesToTopic(transport.ToTopic(chatID))
topics := []types.TopicType{topic}
// Time range of 1 week
startDate := time.Date(2020, 1, 1, 00, 00, 00, 0, time.UTC)
endDate := time.Date(2020, 1, 7, 00, 00, 00, 0, time.UTC)
// 7 days partition, this should create one archive
partition := 7 * 24 * time.Hour
message1 := buildMessage(startDate.Add(1*time.Hour), topic, []byte{1})
err = s.manager.StoreWakuMessage(&message1)
s.Require().NoError(err)
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
index, err := s.manager.LoadHistoryArchiveIndexFromFile(community.ID())
s.Require().NoError(err)
s.Require().Len(index.Archives, 1)
// Time range of next week
startDate = time.Date(2020, 1, 7, 00, 00, 00, 0, time.UTC)
endDate = time.Date(2020, 1, 14, 00, 00, 00, 0, time.UTC)
message2 := buildMessage(startDate.Add(2*time.Hour), topic, []byte{2})
err = s.manager.StoreWakuMessage(&message2)
s.Require().NoError(err)
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
index, err = s.manager.LoadHistoryArchiveIndexFromFile(community.ID())
s.Require().NoError(err)
s.Require().Len(index.Archives, 2)
}
func (s *ManagerSuite) TestSeedHistoryArchiveTorrent() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
err := s.manager.StartTorrentClient()
s.Require().NoError(err)
defer s.manager.StopTorrentClient()
community, chatID, err := s.buildCommunityWithChat()
s.Require().NoError(err)
topic := types.BytesToTopic(transport.ToTopic(chatID))
topics := []types.TopicType{topic}
startDate := time.Date(2020, 1, 1, 00, 00, 00, 0, time.UTC)
endDate := time.Date(2020, 1, 7, 00, 00, 00, 0, time.UTC)
partition := 7 * 24 * time.Hour
message1 := buildMessage(startDate.Add(1*time.Hour), topic, []byte{1})
err = s.manager.StoreWakuMessage(&message1)
s.Require().NoError(err)
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
err = s.manager.SeedHistoryArchiveTorrent(community.ID())
s.Require().NoError(err)
s.Require().Len(s.manager.torrentTasks, 1)
metaInfoHash := s.manager.torrentTasks[community.IDString()]
torrent, ok := s.manager.torrentClient.Torrent(metaInfoHash)
defer torrent.Drop()
s.Require().Equal(ok, true)
s.Require().Equal(torrent.Seeding(), true)
}
func (s *ManagerSuite) TestUnseedHistoryArchiveTorrent() {
torrentConfig := buildTorrentConfig()
s.manager.SetTorrentConfig(&torrentConfig)
err := s.manager.StartTorrentClient()
s.Require().NoError(err)
defer s.manager.StopTorrentClient()
community, chatID, err := s.buildCommunityWithChat()
s.Require().NoError(err)
topic := types.BytesToTopic(transport.ToTopic(chatID))
topics := []types.TopicType{topic}
startDate := time.Date(2020, 1, 1, 00, 00, 00, 0, time.UTC)
endDate := time.Date(2020, 1, 7, 00, 00, 00, 0, time.UTC)
partition := 7 * 24 * time.Hour
message1 := buildMessage(startDate.Add(1*time.Hour), topic, []byte{1})
err = s.manager.StoreWakuMessage(&message1)
s.Require().NoError(err)
_, err = s.manager.CreateHistoryArchiveTorrent(community.ID(), topics, startDate, endDate, partition)
s.Require().NoError(err)
err = s.manager.SeedHistoryArchiveTorrent(community.ID())
s.Require().NoError(err)
s.Require().Len(s.manager.torrentTasks, 1)
metaInfoHash := s.manager.torrentTasks[community.IDString()]
s.manager.UnseedHistoryArchiveTorrent(community.ID())
_, ok := s.manager.torrentClient.Torrent(metaInfoHash)
s.Require().Equal(ok, false)
}
func buildTorrentConfig() params.TorrentConfig {
torrentConfig := params.TorrentConfig{
Enabled: true,
DataDir: os.TempDir() + "/archivedata",
TorrentDir: os.TempDir() + "/torrents",
Port: 9999,
}
return torrentConfig
}
func buildMessage(timestamp time.Time, topic types.TopicType, hash []byte) types.Message {
message := types.Message{
Sig: []byte{1},
Timestamp: uint32(timestamp.Unix()),
Topic: topic,
Payload: []byte{1},
Padding: []byte{1},
Hash: hash,
}
return message
}
func (s *ManagerSuite) buildCommunityWithChat() (*Community, string, error) {
createRequest := &requests.CreateCommunity{
Name: "status",
Description: "status community description",
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
}
community, err := s.manager.CreateCommunity(createRequest, true)
if err != nil {
return nil, "", err
}
chat := &protobuf.CommunityChat{
Identity: &protobuf.ChatIdentity{
DisplayName: "added-chat",
Description: "description",
},
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
},
Members: make(map[string]*protobuf.CommunityMember),
}
_, changes, err := s.manager.CreateChat(community.ID(), chat, true)
if err != nil {
return nil, "", err
}
chatID := ""
for cID := range changes.ChatsAdded {
chatID = cID
break
}
return community, chatID, nil
}