status-go/protocol/communities_messenger_test.go

1566 lines
49 KiB
Go
Raw Normal View History

package protocol
import (
2021-01-11 10:32:51 +00:00
"bytes"
"context"
"crypto/ecdsa"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
"github.com/status-im/status-go/multiaccounts/settings"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/protocol/common"
2021-01-11 10:32:51 +00:00
"github.com/status-im/status-go/protocol/communities"
"github.com/status-im/status-go/protocol/encryption/multidevice"
"github.com/status-im/status-go/protocol/protobuf"
2021-01-11 10:32:51 +00:00
"github.com/status-im/status-go/protocol/requests"
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/waku"
)
func TestMessengerCommunitiesSuite(t *testing.T) {
suite.Run(t, new(MessengerCommunitiesSuite))
}
type MessengerCommunitiesSuite struct {
suite.Suite
bob *Messenger
alice *Messenger
// If one wants to send messages between different instances of Messenger,
// a single Waku service should be shared.
shh types.Waku
logger *zap.Logger
}
func (s *MessengerCommunitiesSuite) SetupTest() {
s.logger = tt.MustCreateTestLogger()
config := waku.DefaultConfig
config.MinimumAcceptedPoW = 0
shh := waku.New(&config, s.logger)
s.shh = gethbridge.NewGethWakuWrapper(shh)
s.Require().NoError(shh.Start())
2020-12-22 11:00:13 +00:00
s.bob = s.newMessenger()
s.alice = s.newMessenger()
_, err := s.bob.Start()
s.Require().NoError(err)
_, err = s.alice.Start()
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSuite) TearDownTest() {
s.Require().NoError(s.bob.Shutdown())
s.Require().NoError(s.alice.Shutdown())
_ = s.logger.Sync()
}
func (s *MessengerCommunitiesSuite) newMessengerWithOptions(shh types.Waku, privateKey *ecdsa.PrivateKey, options []Option) *Messenger {
m, err := NewMessenger(
"Test",
privateKey,
&testNode{shh: shh},
uuid.New().String(),
2022-01-12 16:02:01 +00:00
nil,
options...,
)
s.Require().NoError(err)
err = m.Init()
s.Require().NoError(err)
config := params.NodeConfig{
NetworkID: 10,
DataDir: "test",
}
networks := json.RawMessage("{}")
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
setting := settings.Settings{
Address: types.HexToAddress("0x1122334455667788990011223344556677889900"),
AnonMetricsShouldSend: false,
CurrentNetwork: "mainnet_rpc",
DappsAddress: types.HexToAddress("0x1122334455667788990011223344556677889900"),
InstallationID: "d3efcff6-cffa-560e-a547-21d3858cbc51",
KeyUID: "0x1122334455667788990011223344556677889900",
LatestDerivedPath: 0,
Name: "Test",
Networks: &networks,
PhotoPath: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAIAAACRXR/mAAAAjklEQVR4nOzXwQmFMBAAUZXUYh32ZB32ZB02sxYQQSZGsod55/91WFgSS0RM+SyjA56ZRZhFmEWYRRT6h+M6G16zrxv6fdJpmUWYRbxsYr13dKfanpN0WmYRZhGzXz6AWYRZRIfbaX26fT9Jk07LLMIsosPt9I/dTDotswizCG+nhFmEWYRZhFnEHQAA///z1CFkYamgfQAAAABJRU5ErkJggg==",
PreviewPrivacy: false,
PublicKey: "0x04112233445566778899001122334455667788990011223344556677889900112233445566778899001122334455667788990011223344556677889900",
SigningPhrase: "yurt joey vibe",
SendPushNotifications: true,
ProfilePicturesVisibility: 1,
DefaultSyncPeriod: 86400,
UseMailservers: true,
LinkPreviewRequestEnabled: true,
SendStatusUpdates: true,
WalletRootAddress: types.HexToAddress("0x1122334455667788990011223344556677889900")}
Sync Settings (#2478) * Sync Settings * Added valueHandlers and Database singleton Some issues remain, need a way to comparing incoming sql.DB to check if the connection is to a different file or not. Maybe make singleton instance per filename * Added functionality to check the sqlite filename * Refactor of Database.SaveSyncSettings to be used as a handler * Implemented inteface for setting sync protobuf factories * Refactored and completed adhoc send setting sync * Tidying up * Immutability refactor * Refactor settings into dedicated package * Breakout structs * Tidy up * Refactor of bulk settings sync * Bug fixes * Addressing feedback * Fix code dropped during rebase * Fix for db closed * Fix for node config related crashes * Provisional fix for type assertion - issue 2 * Adding robust type assertion checks * Partial fix for null literal db storage and json encoding * Fix for passively handling nil sql.DB, and checking if elem has len and if len is 0 * Added test for preferred name behaviour * Adding saved sync settings to MessengerResponse * Completed granular initial sync and clock from network on save * add Settings to isEmpty * Refactor of protobufs, partially done * Added syncSetting receiver handling, some bug fixes * Fix for sticker packs * Implement inactive flag on sync protobuf factory * Refactor of types and structs * Added SettingField.CanSync functionality * Addressing rebase artifact * Refactor of Setting SELECT queries * Refactor of string return queries * VERSION bump and migration index bump * Deactiveate Sync Settings * Deactiveated preferred_name and send_status_updates Co-authored-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
2022-03-23 18:47:00 +00:00
_ = m.settings.CreateSettings(setting, config)
return m
}
func (s *MessengerCommunitiesSuite) newMessengerWithKey(shh types.Waku, privateKey *ecdsa.PrivateKey) *Messenger {
tmpFile, err := ioutil.TempFile("", "")
s.Require().NoError(err)
options := []Option{
WithCustomLogger(s.logger),
WithDatabaseConfig(tmpFile.Name(), ""),
WithDatasync(),
}
return s.newMessengerWithOptions(shh, privateKey, options)
}
2020-12-22 11:00:13 +00:00
func (s *MessengerCommunitiesSuite) newMessenger() *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
return s.newMessengerWithKey(s.shh, privateKey)
}
func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() {
2020-12-22 11:00:13 +00:00
alice := s.newMessenger()
2021-01-11 10:32:51 +00:00
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.CommunitiesSettings(), 1)
2021-01-11 10:32:51 +00:00
community := response.Communities()[0]
communitySettings := response.CommunitiesSettings()[0]
s.Require().Equal(communitySettings.CommunityID, community.IDString())
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
// Send an community message
chat := CreateOneToOneChat(common.PubkeyToHex(&alice.identity.PublicKey), &alice.identity.PublicKey, s.alice.transport)
inputMessage := &common.Message{}
inputMessage.ChatId = chat.ID
inputMessage.Text = "some text"
inputMessage.CommunityID = community.IDString()
2021-01-11 10:32:51 +00:00
err = s.bob.SaveChat(chat)
s.Require().NoError(err)
_, err = s.bob.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = alice.RetrieveAll()
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
communities, err := alice.Communities()
s.Require().NoError(err)
s.Require().Len(communities, 2)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
2021-06-03 13:11:55 +00:00
s.Require().Len(response.Messages(), 1)
s.Require().Equal(community.IDString(), response.Messages()[0].CommunityID)
}
func (s *MessengerCommunitiesSuite) TestJoinCommunity() {
ctx := context.Background()
2021-01-11 10:32:51 +00:00
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.CommunitiesSettings(), 1)
communitySettings := response.CommunitiesSettings()[0]
2021-01-11 10:32:51 +00:00
community := response.Communities()[0]
s.Require().Equal(communitySettings.CommunityID, community.IDString())
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
orgChat := &protobuf.CommunityChat{
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
},
Identity: &protobuf.ChatIdentity{
DisplayName: "status-core",
Emoji: "😎",
Description: "status-core community chat",
},
}
2021-01-11 10:32:51 +00:00
response, err = s.bob.CreateCommunityChat(community.ID(), orgChat)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.Chats(), 1)
2021-01-11 10:32:51 +00:00
createdChat := response.Chats()[0]
s.Require().Equal(community.IDString(), createdChat.CommunityID)
s.Require().Equal(orgChat.Identity.DisplayName, createdChat.Name)
s.Require().Equal(orgChat.Identity.Emoji, createdChat.Emoji)
s.Require().NotEmpty(createdChat.ID)
s.Require().Equal(ChatTypeCommunityChat, createdChat.ChatType)
s.Require().True(createdChat.Active)
s.Require().NotEmpty(createdChat.Timestamp)
s.Require().True(strings.HasPrefix(createdChat.ID, community.IDString()))
// Make sure the changes are reflect in the community
community = response.Communities()[0]
var chatIds []string
for k := range community.Chats() {
chatIds = append(chatIds, k)
}
category := &requests.CreateCommunityCategory{
CommunityID: community.ID(),
CategoryName: "category-name",
ChatIDs: chatIds,
}
response, err = s.bob.CreateCommunityCategory(category)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.Communities()[0].Categories(), 1)
// Make sure the changes are reflect in the community
2021-01-11 10:32:51 +00:00
community = response.Communities()[0]
chats := community.Chats()
s.Require().Len(chats, 1)
// Send an community message
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.bob.transport)
inputMessage := &common.Message{}
inputMessage.ChatId = chat.ID
inputMessage.Text = "some text"
inputMessage.CommunityID = community.IDString()
2021-01-11 10:32:51 +00:00
err = s.bob.SaveChat(chat)
s.Require().NoError(err)
_, err = s.bob.SendChatMessage(context.Background(), inputMessage)
s.Require().NoError(err)
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
communities, err := s.alice.Communities()
s.Require().NoError(err)
s.Require().Len(communities, 2)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
2021-06-03 13:11:55 +00:00
s.Require().Len(response.Messages(), 1)
s.Require().Equal(community.IDString(), response.Messages()[0].CommunityID)
// We join the org
response, err = s.alice.JoinCommunity(ctx, community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].Joined())
s.Require().Len(response.Chats(), 1)
s.Require().Len(response.Communities()[0].Categories(), 1)
var categoryID string
for k := range response.Communities()[0].Categories() {
categoryID = k
}
// The chat should be created
2021-01-11 10:32:51 +00:00
createdChat = response.Chats()[0]
s.Require().Equal(community.IDString(), createdChat.CommunityID)
s.Require().Equal(orgChat.Identity.DisplayName, createdChat.Name)
s.Require().Equal(orgChat.Identity.Emoji, createdChat.Emoji)
s.Require().NotEmpty(createdChat.ID)
s.Require().Equal(ChatTypeCommunityChat, createdChat.ChatType)
s.Require().Equal(categoryID, createdChat.CategoryID)
s.Require().True(createdChat.Active)
s.Require().NotEmpty(createdChat.Timestamp)
s.Require().True(strings.HasPrefix(createdChat.ID, community.IDString()))
// Create another org chat
orgChat = &protobuf.CommunityChat{
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
},
Identity: &protobuf.ChatIdentity{
DisplayName: "status-core-ui",
Emoji: "👍",
Description: "status-core-ui community chat",
},
}
2021-01-11 10:32:51 +00:00
response, err = s.bob.CreateCommunityChat(community.ID(), orgChat)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.Chats(), 1)
var actualChat *Chat
// Pull message, this time it should be received as advertised automatically
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
2021-10-06 10:08:35 +00:00
if len(response.Communities()) != 1 {
return errors.New("community not received")
}
for _, c := range response.Chats() {
if c.Name == orgChat.Identity.DisplayName {
actualChat = c
return nil
}
2021-10-06 10:08:35 +00:00
}
return errors.New("chat not found")
})
s.Require().NoError(err)
communities, err = s.alice.Communities()
s.Require().NoError(err)
s.Require().Len(communities, 2)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().NotNil(actualChat)
2021-10-06 10:08:35 +00:00
s.Require().Equal(community.IDString(), actualChat.CommunityID)
s.Require().Equal(orgChat.Identity.DisplayName, actualChat.Name)
s.Require().Equal(orgChat.Identity.Emoji, actualChat.Emoji)
s.Require().NotEmpty(actualChat.ID)
s.Require().Equal(ChatTypeCommunityChat, actualChat.ChatType)
s.Require().True(actualChat.Active)
s.Require().NotEmpty(actualChat.Timestamp)
s.Require().True(strings.HasPrefix(actualChat.ID, community.IDString()))
// We leave the org
2021-01-11 10:32:51 +00:00
response, err = s.alice.LeaveCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().False(response.Communities()[0].Joined())
s.Require().Len(response.RemovedChats(), 2)
}
2021-01-11 10:32:51 +00:00
func (s *MessengerCommunitiesSuite) TestInviteUsersToCommunity() {
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].HasMember(&s.bob.identity.PublicKey))
s.Require().True(response.Communities()[0].IsMemberAdmin(&s.bob.identity.PublicKey))
2021-01-11 10:32:51 +00:00
community := response.Communities()[0]
2021-01-11 10:32:51 +00:00
response, err = s.bob.InviteUsersToCommunity(
&requests.InviteUsersToCommunity{
CommunityID: community.ID(),
Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)},
},
)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
2021-01-11 10:32:51 +00:00
community = response.Communities()[0]
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
communities, err := s.alice.Communities()
s.Require().NoError(err)
s.Require().Len(communities, 2)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
2021-01-11 10:32:51 +00:00
community = response.Communities()[0]
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
}
func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() {
2021-01-11 10:32:51 +00:00
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_INVITATION_ONLY,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
2021-01-11 10:32:51 +00:00
community := response.Communities()[0]
// Create chat
orgChat := &protobuf.CommunityChat{
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_NO_MEMBERSHIP,
},
Identity: &protobuf.ChatIdentity{
DisplayName: "status-core",
Description: "status-core community chat",
},
}
2021-01-11 10:32:51 +00:00
response, err = s.bob.CreateCommunityChat(community.ID(), orgChat)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.Chats(), 1)
2021-01-11 10:32:51 +00:00
response, err = s.bob.InviteUsersToCommunity(
&requests.InviteUsersToCommunity{
CommunityID: community.ID(),
Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)},
},
)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
2021-01-11 10:32:51 +00:00
community = response.Communities()[0]
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
communities, err := s.alice.Communities()
s.Require().NoError(err)
s.Require().Len(communities, 2)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
ctx := context.Background()
// We join the org
response, err = s.alice.JoinCommunity(ctx, community.ID())
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].Joined())
s.Require().Len(response.Chats(), 1)
2021-01-11 10:32:51 +00:00
chatID := response.Chats()[0].ID
inputMessage := &common.Message{}
inputMessage.ChatId = chatID
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
inputMessage.Text = "some text"
_, err = s.alice.SendChatMessage(ctx, inputMessage)
s.Require().NoError(err)
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.bob.RetrieveAll()
if err != nil {
return err
}
2021-06-03 13:11:55 +00:00
if len(response.messages) == 0 {
return errors.New("message not received")
}
return nil
})
s.Require().NoError(err)
2021-06-03 13:11:55 +00:00
s.Require().Len(response.Messages(), 1)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Chats(), 1)
s.Require().Equal(chatID, response.Chats()[0].ID)
}
func (s *MessengerCommunitiesSuite) TestImportCommunity() {
ctx := context.Background()
2021-01-11 10:32:51 +00:00
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.CommunitiesSettings(), 1)
2021-01-11 10:32:51 +00:00
community := response.Communities()[0]
communitySettings := response.CommunitiesSettings()[0]
s.Require().Equal(communitySettings.CommunityID, community.IDString())
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
category := &requests.CreateCommunityCategory{
CommunityID: community.ID(),
CategoryName: "category-name",
ChatIDs: []string{},
}
response, err = s.bob.CreateCommunityCategory(category)
2022-03-28 10:10:40 +00:00
s.Require().NoError(err)
community = response.Communities()[0]
2021-01-11 10:32:51 +00:00
privateKey, err := s.bob.ExportCommunity(community.ID())
s.Require().NoError(err)
_, err = s.alice.ImportCommunity(ctx, privateKey)
s.Require().NoError(err)
// Invite user on bob side
newUser, err := crypto.GenerateKey()
s.Require().NoError(err)
2021-01-11 10:32:51 +00:00
_, err = s.bob.InviteUsersToCommunity(
&requests.InviteUsersToCommunity{
CommunityID: community.ID(),
Users: []types.HexBytes{common.PubkeyToHexBytes(&newUser.PublicKey)},
},
)
s.Require().NoError(err)
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
2021-01-11 10:32:51 +00:00
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
2021-01-11 10:32:51 +00:00
s.Require().Len(response.Communities(), 1)
s.Require().Len(response.Communities()[0].Categories(), 1)
2021-01-11 10:32:51 +00:00
community = response.Communities()[0]
s.Require().True(community.Joined())
s.Require().True(community.IsAdmin())
}
2021-01-11 10:32:51 +00:00
func (s *MessengerCommunitiesSuite) TestRequestAccess() {
ctx := context.Background()
2021-01-11 10:32:51 +00:00
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community := response.Communities()[0]
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport)
s.Require().NoError(s.bob.SaveChat(chat))
message := buildTestMessage(*chat)
message.CommunityID = community.IDString()
// We send a community link to alice
response, err = s.bob.SendChatMessage(ctx, message)
2021-01-11 10:32:51 +00:00
s.Require().NoError(err)
s.Require().NotNil(response)
// Retrieve community link & community
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("message not received")
}
return nil
})
s.Require().NoError(err)
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// We try to join the org
response, err = s.alice.RequestToJoinCommunity(request)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin1 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin1)
s.Require().Equal(community.ID(), requestToJoin1.CommunityID)
s.Require().True(requestToJoin1.Our)
s.Require().NotEmpty(requestToJoin1.ID)
s.Require().NotEmpty(requestToJoin1.Clock)
s.Require().Equal(requestToJoin1.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
// Make sure clock is not empty
s.Require().NotEmpty(requestToJoin1.Clock)
s.Require().Len(response.Communities(), 1)
s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin1.Clock)
// pull all communities to make sure we set RequestedToJoinAt
allCommunities, err := s.alice.Communities()
s.Require().NoError(err)
s.Require().Len(allCommunities, 2)
if bytes.Equal(allCommunities[0].ID(), community.ID()) {
s.Require().Equal(allCommunities[0].RequestedToJoinAt(), requestToJoin1.Clock)
} else {
s.Require().Equal(allCommunities[1].RequestedToJoinAt(), requestToJoin1.Clock)
}
// pull to make sure it has been saved
requestsToJoin, err := s.alice.MyPendingRequestsToJoin()
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 1)
// Make sure the requests are fetched also by community
requestsToJoin, err = s.alice.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 1)
// Retrieve request to join
err = tt.RetryWithBackOff(func() error {
response, err = s.bob.RetrieveAll()
if err != nil {
return err
}
if len(response.RequestsToJoinCommunity) == 0 {
return errors.New("request to join community not received")
}
return nil
})
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin2 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin2)
s.Require().Equal(community.ID(), requestToJoin2.CommunityID)
s.Require().False(requestToJoin2.Our)
s.Require().NotEmpty(requestToJoin2.ID)
s.Require().NotEmpty(requestToJoin2.Clock)
s.Require().Equal(requestToJoin2.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin2.State)
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
// Accept request
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID}
response, err = s.bob.AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
updatedCommunity := response.Communities()[0]
s.Require().NotNil(updatedCommunity)
s.Require().True(updatedCommunity.HasMember(&s.alice.identity.PublicKey))
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
aliceCommunity := response.Communities()[0]
s.Require().Equal(community.ID(), aliceCommunity.ID())
s.Require().True(aliceCommunity.HasMember(&s.alice.identity.PublicKey))
// Community should be joined at this point
s.Require().True(aliceCommunity.Joined())
// Make sure the requests are not pending on either sides
requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 0)
requestsToJoin, err = s.alice.MyPendingRequestsToJoin()
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 0)
}
func (s *MessengerCommunitiesSuite) TestRequestAccessAgain() {
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community := response.Communities()[0]
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport)
s.Require().NoError(s.bob.SaveChat(chat))
message := buildTestMessage(*chat)
message.CommunityID = community.IDString()
// We send a community link to alice
response, err = s.bob.SendChatMessage(context.Background(), message)
s.Require().NoError(err)
s.Require().NotNil(response)
// Retrieve community link & community
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("message not received")
}
return nil
})
s.Require().NoError(err)
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// We try to join the org
response, err = s.alice.RequestToJoinCommunity(request)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin1 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin1)
s.Require().Equal(community.ID(), requestToJoin1.CommunityID)
s.Require().True(requestToJoin1.Our)
s.Require().NotEmpty(requestToJoin1.ID)
s.Require().NotEmpty(requestToJoin1.Clock)
s.Require().Equal(requestToJoin1.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
// Make sure clock is not empty
s.Require().NotEmpty(requestToJoin1.Clock)
s.Require().Len(response.Communities(), 1)
s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin1.Clock)
// pull all communities to make sure we set RequestedToJoinAt
allCommunities, err := s.alice.Communities()
s.Require().NoError(err)
s.Require().Len(allCommunities, 2)
if bytes.Equal(allCommunities[0].ID(), community.ID()) {
s.Require().Equal(allCommunities[0].RequestedToJoinAt(), requestToJoin1.Clock)
} else {
s.Require().Equal(allCommunities[1].RequestedToJoinAt(), requestToJoin1.Clock)
}
// pull to make sure it has been saved
requestsToJoin, err := s.alice.MyPendingRequestsToJoin()
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 1)
// Make sure the requests are fetched also by community
requestsToJoin, err = s.alice.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 1)
// Retrieve request to join
err = tt.RetryWithBackOff(func() error {
response, err = s.bob.RetrieveAll()
if err != nil {
return err
}
if len(response.RequestsToJoinCommunity) == 0 {
return errors.New("request to join community not received")
}
return nil
})
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin2 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin2)
s.Require().Equal(community.ID(), requestToJoin2.CommunityID)
s.Require().False(requestToJoin2.Our)
s.Require().NotEmpty(requestToJoin2.ID)
s.Require().NotEmpty(requestToJoin2.Clock)
s.Require().Equal(requestToJoin2.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin2.State)
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
// Check that a notification is been added to messenger
notifications := response.Notifications()
s.Require().Len(notifications, 1)
s.Require().NotEqual(notifications[0].ID.Hex(), "0x0000000000000000000000000000000000000000000000000000000000000000")
2021-01-11 10:32:51 +00:00
// Accept request
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: requestToJoin1.ID}
response, err = s.bob.AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
updatedCommunity := response.Communities()[0]
s.Require().NotNil(updatedCommunity)
s.Require().True(updatedCommunity.HasMember(&s.alice.identity.PublicKey))
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
aliceCommunity := response.Communities()[0]
s.Require().Equal(community.ID(), aliceCommunity.ID())
s.Require().True(aliceCommunity.HasMember(&s.alice.identity.PublicKey))
// Community should be joined at this point
s.Require().True(aliceCommunity.Joined())
// Make sure the requests are not pending on either sides
requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 0)
requestsToJoin, err = s.alice.MyPendingRequestsToJoin()
s.Require().NoError(err)
s.Require().Len(requestsToJoin, 0)
// We request again
request2 := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// We try to join the org, it should error as we are already a member
response, err = s.alice.RequestToJoinCommunity(request2)
s.Require().Error(err)
// We kick the member
response, err = s.bob.RemoveUserFromCommunity(
community.ID(),
common.PubkeyToHex(&s.alice.identity.PublicKey),
)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community = response.Communities()[0]
s.Require().False(community.HasMember(&s.alice.identity.PublicKey))
// Alice should then be removed
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("community not received")
}
return nil
})
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
aliceCommunity = response.Communities()[0]
s.Require().Equal(community.ID(), aliceCommunity.ID())
s.Require().False(aliceCommunity.HasMember(&s.alice.identity.PublicKey))
// Alice can request access again
request3 := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err = s.alice.RequestToJoinCommunity(request3)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin3 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin3)
s.Require().Equal(community.ID(), requestToJoin3.CommunityID)
s.Require().True(requestToJoin3.Our)
s.Require().NotEmpty(requestToJoin3.ID)
s.Require().NotEmpty(requestToJoin3.Clock)
s.Require().Equal(requestToJoin3.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin3.State)
s.Require().Len(response.Communities(), 1)
s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin3.Clock)
// Retrieve request to join
err = tt.RetryWithBackOff(func() error {
response, err = s.bob.RetrieveAll()
if err != nil {
return err
}
if len(response.RequestsToJoinCommunity) == 0 {
return errors.New("request to join community not received")
}
return nil
})
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin4 := response.RequestsToJoinCommunity[0]
s.Require().NotNil(requestToJoin4)
s.Require().Equal(community.ID(), requestToJoin4.CommunityID)
s.Require().False(requestToJoin4.Our)
s.Require().NotEmpty(requestToJoin4.ID)
s.Require().NotEmpty(requestToJoin4.Clock)
s.Require().Equal(requestToJoin4.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin4.State)
s.Require().Equal(requestToJoin3.ID, requestToJoin4.ID)
}
func (s *MessengerCommunitiesSuite) TestShareCommunity() {
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community := response.Communities()[0]
response, err = s.bob.ShareCommunity(
&requests.ShareCommunity{
CommunityID: community.ID(),
Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)},
},
)
s.Require().NoError(err)
s.Require().NotNil(response)
2021-06-03 13:11:55 +00:00
s.Require().Len(response.Messages(), 1)
2021-01-11 10:32:51 +00:00
// Add bob to contacts so it does not go on activity center
bobPk := common.PubkeyToHex(&s.alice.identity.PublicKey)
request := &requests.AddContact{ID: types.Hex2Bytes(bobPk)}
_, err = s.alice.AddContact(context.Background(), request)
s.Require().NoError(err)
2021-01-11 10:32:51 +00:00
// Pull message and make sure org is received
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
2021-06-03 13:11:55 +00:00
if len(response.messages) == 0 {
2021-01-11 10:32:51 +00:00
return errors.New("community link not received")
}
return nil
})
s.Require().NoError(err)
2021-06-03 13:11:55 +00:00
s.Require().Len(response.Messages(), 1)
2021-01-11 10:32:51 +00:00
}
func (s *MessengerCommunitiesSuite) TestBanUser() {
description := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "status",
Color: "#ffffff",
Description: "status community description",
}
// Create an community chat
response, err := s.bob.CreateCommunity(description)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community := response.Communities()[0]
response, err = s.bob.InviteUsersToCommunity(
&requests.InviteUsersToCommunity{
CommunityID: community.ID(),
Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)},
},
)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community = response.Communities()[0]
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
response, err = s.bob.BanUserFromCommunity(
&requests.BanUserFromCommunity{
CommunityID: community.ID(),
User: common.PubkeyToHexBytes(&s.alice.identity.PublicKey),
},
)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
community = response.Communities()[0]
s.Require().False(community.HasMember(&s.alice.identity.PublicKey))
s.Require().True(community.IsBanned(&s.alice.identity.PublicKey))
}
// TestSyncCommunity tests basic sync functionality between 2 Messengers
func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
// Create new device
alicesOtherDevice, err := newMessengerWithKey(s.shh, s.alice.identity, s.logger, nil)
s.Require().NoError(err)
tcs, err := alicesOtherDevice.communitiesManager.All()
s.Require().NoError(err, "alicesOtherDevice.communitiesManager.All")
s.Len(tcs, 1, "Must have 1 communities")
// Pair devices
err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, &multidevice.InstallationMetadata{
Name: "their-name",
DeviceType: "their-device-type",
})
s.Require().NoError(err)
s.pairTwoDevices(alicesOtherDevice, s.alice, "their-name", "their-device-type")
// Create a community
createCommunityReq := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "new community",
Color: "#000000",
Description: "new community description",
}
mr, err := s.alice.CreateCommunity(createCommunityReq)
s.Require().NoError(err, "s.alice.CreateCommunity")
var newCommunity *communities.Community
for _, com := range mr.Communities() {
if com.Name() == createCommunityReq.Name {
newCommunity = com
}
}
s.Require().NotNil(newCommunity)
// Check that Alice has 2 communities
cs, err := s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(cs, 2, "Must have 2 communities")
// Wait for the message to reach its destination
err = tt.RetryWithBackOff(func() error {
_, err = alicesOtherDevice.RetrieveAll()
if err != nil {
return err
}
// Do we have a new synced community?
_, err = alicesOtherDevice.communitiesManager.GetSyncedRawCommunity(newCommunity.ID())
if err != nil {
return fmt.Errorf("community with sync not received %w", err)
}
return nil
})
s.Require().NoError(err)
// Count the number of communities in their device
tcs, err = alicesOtherDevice.communitiesManager.All()
s.Require().NoError(err)
s.Len(tcs, 2, "There must be 2 communities")
s.logger.Debug("", zap.Any("tcs", tcs))
// Get the new community from their db
tnc, err := alicesOtherDevice.communitiesManager.GetByID(newCommunity.ID())
s.Require().NoError(err)
// Check the community on their device matched the new community on Alice's device
s.Equal(newCommunity.ID(), tnc.ID())
s.Equal(newCommunity.Name(), tnc.Name())
s.Equal(newCommunity.DescriptionText(), tnc.DescriptionText())
s.Equal(newCommunity.IDString(), tnc.IDString())
s.Equal(newCommunity.PrivateKey(), tnc.PrivateKey())
s.Equal(newCommunity.PublicKey(), tnc.PublicKey())
s.Equal(newCommunity.Verified(), tnc.Verified())
s.Equal(newCommunity.Muted(), tnc.Muted())
s.Equal(newCommunity.Joined(), tnc.Joined())
s.Equal(newCommunity.IsAdmin(), tnc.IsAdmin())
s.Equal(newCommunity.InvitationOnly(), tnc.InvitationOnly())
}
// TestSyncCommunity_RequestToJoin tests more complex pairing and syncing scenario where one paired device
// makes a request to join a community
func (s *MessengerCommunitiesSuite) TestSyncCommunity_RequestToJoin() {
// Set Alice's installation metadata
aim := &multidevice.InstallationMetadata{
Name: "alice's-device",
DeviceType: "alice's-device-type",
}
err := s.alice.SetInstallationMetadata(s.alice.installationID, aim)
s.Require().NoError(err)
// Create Alice's other device
alicesOtherDevice, err := newMessengerWithKey(s.shh, s.alice.identity, s.logger, nil)
s.Require().NoError(err)
im1 := &multidevice.InstallationMetadata{
Name: "alice's-other-device",
DeviceType: "alice's-other-device-type",
}
err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1)
s.Require().NoError(err)
// Pair alice's two devices
s.pairTwoDevices(alicesOtherDevice, s.alice, im1.Name, im1.DeviceType)
s.pairTwoDevices(s.alice, alicesOtherDevice, aim.Name, aim.DeviceType)
// Check bob the admin has only one community
tcs2, err := s.bob.communitiesManager.All()
s.Require().NoError(err, "admin.communitiesManager.All")
s.Len(tcs2, 1, "Must have 1 communities")
// Bob the admin creates a community
createCommunityReq := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_ON_REQUEST,
Name: "new community",
Color: "#000000",
Description: "new community description",
}
mr, err := s.bob.CreateCommunity(createCommunityReq)
s.Require().NoError(err, "CreateCommunity")
s.Require().NotNil(mr)
s.Len(mr.Communities(), 1)
community := mr.Communities()[0]
// Check that admin has 2 communities
acs, err := s.bob.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(acs, 2, "Must have 2 communities")
// Check that Alice has only 1 community on either device
cs, err := s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(cs, 1, "Must have 1 communities")
tcs1, err := alicesOtherDevice.communitiesManager.All()
s.Require().NoError(err, "alicesOtherDevice.communitiesManager.All")
s.Len(tcs1, 1, "Must have 1 communities")
// Bob the admin opens up a 1-1 chat with alice
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport)
s.Require().NoError(s.bob.SaveChat(chat))
// Bob the admin shares with Alice, via public chat, an invite link to the new community
message := buildTestMessage(*chat)
message.CommunityID = community.IDString()
response, err := s.bob.SendChatMessage(context.Background(), message)
s.Require().NoError(err)
s.Require().NotNil(response)
// Retrieve community link & community
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("no communities received from 1-1")
}
return nil
})
s.Require().NoError(err)
// Check that alice now has 2 communities
cs, err = s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(cs, 2, "Must have 2 communities")
for _, c := range cs {
s.False(c.Joined(), "Must not have joined the community")
}
// Alice requests to join the new community
response, err = s.alice.RequestToJoinCommunity(&requests.RequestToJoinCommunity{CommunityID: community.ID()})
s.Require().NoError(err)
s.Require().NotNil(response)
s.Len(response.RequestsToJoinCommunity, 1)
aRtj := response.RequestsToJoinCommunity[0]
s.Require().NotNil(aRtj)
s.Equal(community.ID(), aRtj.CommunityID)
s.True(aRtj.Our)
s.Require().NotEmpty(aRtj.ID)
s.Require().NotEmpty(aRtj.Clock)
s.Equal(aRtj.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Equal(communities.RequestToJoinStatePending, aRtj.State)
// Make sure clock is not empty
s.Require().NotEmpty(aRtj.Clock)
s.Len(response.Communities(), 1)
s.Equal(response.Communities()[0].RequestedToJoinAt(), aRtj.Clock)
// pull all communities to make sure we set RequestedToJoinAt
allCommunities, err := s.alice.Communities()
s.Require().NoError(err)
s.Len(allCommunities, 2)
if bytes.Equal(allCommunities[0].ID(), community.ID()) {
s.Equal(allCommunities[0].RequestedToJoinAt(), aRtj.Clock)
} else {
s.Equal(allCommunities[1].RequestedToJoinAt(), aRtj.Clock)
}
// pull to make sure it has been saved
requestsToJoin, err := s.alice.MyPendingRequestsToJoin()
s.Require().NoError(err)
s.Len(requestsToJoin, 1)
// Make sure the requests are fetched also by community
requestsToJoin, err = s.alice.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Len(requestsToJoin, 1)
// Alice's other device retrieves sync message from the join
err = tt.RetryWithBackOff(func() error {
response, err = alicesOtherDevice.RetrieveAll()
if err != nil {
return err
}
// Do we have a new synced community?
_, err = alicesOtherDevice.communitiesManager.GetSyncedRawCommunity(community.ID())
if err != nil {
return fmt.Errorf("community with sync not received %w", err)
}
// Do we have a new pending request to join for the new community
requestsToJoin, err = alicesOtherDevice.PendingRequestsToJoinForCommunity(community.ID())
if err != nil {
return err
}
if len(requestsToJoin) == 0 {
return errors.New("no requests to join")
}
return nil
})
s.Require().NoError(err)
s.Len(response.Communities(), 1)
// Get the pending requests to join for the new community on alicesOtherDevice
requestsToJoin, err = alicesOtherDevice.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Len(requestsToJoin, 1)
// Check request to join on alicesOtherDevice matches the RTJ on alice
aodRtj := requestsToJoin[0]
s.Equal(aRtj.PublicKey, aodRtj.PublicKey)
s.Equal(aRtj.ID, aodRtj.ID)
s.Equal(aRtj.CommunityID, aodRtj.CommunityID)
s.Equal(aRtj.Clock, aodRtj.Clock)
s.Equal(aRtj.ENSName, aodRtj.ENSName)
s.Equal(aRtj.ChatID, aodRtj.ChatID)
s.Equal(aRtj.State, aodRtj.State)
// Bob the admin retrieves request to join
err = tt.RetryWithBackOff(func() error {
response, err = s.bob.RetrieveAll()
if err != nil {
return err
}
if len(response.RequestsToJoinCommunity) == 0 {
return errors.New("request to join community not received")
}
return nil
})
s.Require().NoError(err)
s.Len(response.RequestsToJoinCommunity, 1)
// Check that bob the admin's newly received request to join matches what we expect
bobRtj := response.RequestsToJoinCommunity[0]
s.Require().NotNil(bobRtj)
s.Equal(community.ID(), bobRtj.CommunityID)
s.False(bobRtj.Our)
s.Require().NotEmpty(bobRtj.ID)
s.Require().NotEmpty(bobRtj.Clock)
s.Equal(bobRtj.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
s.Equal(communities.RequestToJoinStatePending, bobRtj.State)
s.Equal(aRtj.PublicKey, bobRtj.PublicKey)
s.Equal(aRtj.ID, bobRtj.ID)
s.Equal(aRtj.CommunityID, bobRtj.CommunityID)
s.Equal(aRtj.Clock, bobRtj.Clock)
s.Equal(aRtj.ENSName, bobRtj.ENSName)
s.Equal(aRtj.ChatID, bobRtj.ChatID)
s.Equal(aRtj.State, bobRtj.State)
}
func (s *MessengerCommunitiesSuite) pairTwoDevices(device1, device2 *Messenger, deviceName, deviceType string) {
// Send pairing data
response, err := device1.SendPairInstallation(context.Background())
s.Require().NoError(err)
s.Require().NotNil(response)
s.Len(response.Chats(), 1)
s.False(response.Chats()[0].Active)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
device2,
func(r *MessengerResponse) bool {
for _, installation := range r.Installations {
if installation.ID == device1.installationID {
return installation.InstallationMetadata != nil && deviceName == installation.InstallationMetadata.Name && deviceType == installation.InstallationMetadata.DeviceType
}
}
return false
},
"installation not received",
)
s.Require().NoError(err)
s.Require().NotNil(response)
// Ensure installation is enabled
err = device2.EnableInstallation(device1.installationID)
s.Require().NoError(err)
}
func (s *MessengerCommunitiesSuite) TestSyncCommunity_Leave() {
// Set Alice's installation metadata
aim := &multidevice.InstallationMetadata{
Name: "alice's-device",
DeviceType: "alice's-device-type",
}
err := s.alice.SetInstallationMetadata(s.alice.installationID, aim)
s.Require().NoError(err)
// Create Alice's other device
alicesOtherDevice, err := newMessengerWithKey(s.shh, s.alice.identity, s.logger, nil)
s.Require().NoError(err)
im1 := &multidevice.InstallationMetadata{
Name: "alice's-other-device",
DeviceType: "alice's-other-device-type",
}
err = alicesOtherDevice.SetInstallationMetadata(alicesOtherDevice.installationID, im1)
s.Require().NoError(err)
// Pair alice's two devices
s.pairTwoDevices(alicesOtherDevice, s.alice, im1.Name, im1.DeviceType)
s.pairTwoDevices(s.alice, alicesOtherDevice, aim.Name, aim.DeviceType)
// Check bob the admin has only one community
tcs2, err := s.bob.communitiesManager.All()
s.Require().NoError(err, "admin.communitiesManager.All")
s.Len(tcs2, 1, "Must have 1 communities")
// Bob the admin creates a community
createCommunityReq := &requests.CreateCommunity{
Membership: protobuf.CommunityPermissions_NO_MEMBERSHIP,
Name: "new community",
Color: "#000000",
Description: "new community description",
}
mr, err := s.bob.CreateCommunity(createCommunityReq)
s.Require().NoError(err, "CreateCommunity")
s.Require().NotNil(mr)
s.Len(mr.Communities(), 1)
community := mr.Communities()[0]
// Check that admin has 2 communities
acs, err := s.bob.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(acs, 2, "Must have 2 communities")
// Check that Alice has only 1 community on either device
cs, err := s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(cs, 1, "Must have 1 communities")
tcs1, err := alicesOtherDevice.communitiesManager.All()
s.Require().NoError(err, "alicesOtherDevice.communitiesManager.All")
s.Len(tcs1, 1, "Must have 1 communities")
// Bob the admin opens up a 1-1 chat with alice
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport)
s.Require().NoError(s.bob.SaveChat(chat))
// Bob the admin shares with Alice, via public chat, an invite link to the new community
message := buildTestMessage(*chat)
message.CommunityID = community.IDString()
response, err := s.bob.SendChatMessage(context.Background(), message)
s.Require().NoError(err)
s.Require().NotNil(response)
// Retrieve community link & community
err = tt.RetryWithBackOff(func() error {
response, err = s.alice.RetrieveAll()
if err != nil {
return err
}
if len(response.Communities()) == 0 {
return errors.New("no communities received from 1-1")
}
return nil
})
s.Require().NoError(err)
// Check that alice now has 2 communities
cs, err = s.alice.communitiesManager.All()
s.Require().NoError(err, "communitiesManager.All")
s.Len(cs, 2, "Must have 2 communities")
for _, c := range cs {
s.False(c.Joined(), "Must not have joined the community")
}
// alice joins the community
mr, err = s.alice.JoinCommunity(context.Background(), community.ID())
s.Require().NoError(err, "s.alice.JoinCommunity")
s.Require().NotNil(mr)
s.Len(mr.Communities(), 1)
aCom := mr.Communities()[0]
// Check that the joined community has the correct values
s.Equal(community.ID(), aCom.ID())
s.Equal(community.Clock(), aCom.Clock())
s.Equal(community.PublicKey(), aCom.PublicKey())
// Check alicesOtherDevice receives the sync join message
err = tt.RetryWithBackOff(func() error {
response, err = alicesOtherDevice.RetrieveAll()
if err != nil {
return err
}
// Do we have a new synced community?
_, err = alicesOtherDevice.communitiesManager.GetSyncedRawCommunity(community.ID())
if err != nil {
return fmt.Errorf("community with sync not received %w", err)
}
return nil
})
s.Require().NoError(err)
s.Len(response.Communities(), 1, "")
aoCom := mr.Communities()[0]
s.Equal(aCom, aoCom)
}