4835 lines
163 KiB
Go
4835 lines
163 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/tls"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/stretchr/testify/suite"
|
|
"go.uber.org/zap"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
|
|
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"
|
|
"github.com/status-im/status-go/images"
|
|
"github.com/status-im/status-go/multiaccounts/accounts"
|
|
multiaccountscommon "github.com/status-im/status-go/multiaccounts/common"
|
|
"github.com/status-im/status-go/protocol/common"
|
|
"github.com/status-im/status-go/protocol/communities"
|
|
"github.com/status-im/status-go/protocol/discord"
|
|
"github.com/status-im/status-go/protocol/encryption/multidevice"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
"github.com/status-im/status-go/protocol/requests"
|
|
"github.com/status-im/status-go/protocol/transport"
|
|
"github.com/status-im/status-go/protocol/tt"
|
|
v1protocol "github.com/status-im/status-go/protocol/v1"
|
|
"github.com/status-im/status-go/server"
|
|
localnotifications "github.com/status-im/status-go/services/local-notifications"
|
|
"github.com/status-im/status-go/waku"
|
|
)
|
|
|
|
func TestMessengerCommunitiesSuite(t *testing.T) {
|
|
suite.Run(t, new(MessengerCommunitiesSuite))
|
|
}
|
|
|
|
type MessengerCommunitiesSuite struct {
|
|
suite.Suite
|
|
owner *Messenger
|
|
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())
|
|
|
|
s.owner = s.newMessenger()
|
|
s.owner.account.CustomizationColor = multiaccountscommon.CustomizationColorOrange
|
|
s.bob = s.newMessenger()
|
|
s.bob.account.CustomizationColor = multiaccountscommon.CustomizationColorBlue
|
|
s.alice = s.newMessenger()
|
|
s.alice.account.CustomizationColor = multiaccountscommon.CustomizationColorArmy
|
|
|
|
s.owner.communitiesManager.RekeyInterval = 50 * time.Millisecond
|
|
|
|
_, err := s.owner.Start()
|
|
s.Require().NoError(err)
|
|
_, err = s.bob.Start()
|
|
s.Require().NoError(err)
|
|
_, err = s.alice.Start()
|
|
s.Require().NoError(err)
|
|
|
|
s.setMessengerDisplayName(s.owner, "Charlie")
|
|
s.setMessengerDisplayName(s.bob, "Bobby")
|
|
s.setMessengerDisplayName(s.alice, "Alice")
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TearDownTest() {
|
|
TearDownMessenger(&s.Suite, s.owner)
|
|
TearDownMessenger(&s.Suite, s.bob)
|
|
TearDownMessenger(&s.Suite, s.alice)
|
|
_ = s.logger.Sync()
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) newMessengerWithKey(privateKey *ecdsa.PrivateKey) *Messenger {
|
|
return newTestCommunitiesMessenger(&s.Suite, s.shh, testCommunitiesMessengerConfig{
|
|
testMessengerConfig: testMessengerConfig{
|
|
privateKey: privateKey,
|
|
logger: s.logger,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) newMessenger() *Messenger {
|
|
privateKey, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
|
|
return s.newMessengerWithKey(privateKey)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) setMessengerDisplayName(m *Messenger, name string) {
|
|
profileKp := accounts.GetProfileKeypairForTest(true, false, false)
|
|
profileKp.KeyUID = m.account.KeyUID
|
|
profileKp.Name = DefaultProfileDisplayName
|
|
profileKp.Accounts[0].KeyUID = m.account.KeyUID
|
|
|
|
err := m.settings.SaveOrUpdateKeypair(profileKp)
|
|
s.Require().NoError(err)
|
|
|
|
err = m.SetDisplayName(name)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCreateCommunity() {
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCreateCommunity_WithoutDefaultChannel() {
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
response, err := s.bob.CreateCommunity(description, false)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 0)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRetrieveCommunity() {
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.CommunitiesSettings(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
community := response.Communities()[0]
|
|
communitySettings := response.CommunitiesSettings()[0]
|
|
|
|
s.Require().Equal(communitySettings.CommunityID, community.IDString())
|
|
s.Require().Equal(communitySettings.HistoryArchiveSupportEnabled, false)
|
|
|
|
// Send a community message
|
|
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.alice.transport)
|
|
|
|
inputMessage := common.NewMessage()
|
|
inputMessage.ChatId = chat.ID
|
|
inputMessage.Text = "some text"
|
|
inputMessage.CommunityID = community.IDString()
|
|
|
|
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
|
|
}
|
|
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, 1)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(community.IDString(), response.Messages()[0].CommunityID)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestJoiningOpenCommunityReturnsChatsResponse() {
|
|
ctx := context.Background()
|
|
|
|
openCommunityDescription := &requests.CreateCommunity{
|
|
Name: "open community",
|
|
Description: "open community to join with no requests",
|
|
Color: "#26a69a",
|
|
HistoryArchiveSupportEnabled: true,
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
PinMessageAllMembersEnabled: false,
|
|
}
|
|
|
|
response, err := s.bob.CreateCommunity(openCommunityDescription, true)
|
|
generalChannelChatID := response.Chats()[0].ID
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.CommunitiesSettings(), 1)
|
|
s.Require().Len(response.Chats(), 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()
|
|
|
|
// Bob sends the community link to Alice
|
|
response, err = s.bob.SendChatMessage(ctx, message)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
// Retrieve community link & community for Alice
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) > 0
|
|
},
|
|
"message not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
// Alice request to join community
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
|
|
response, err = s.alice.RequestToJoinCommunity(request)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
requestToJoin := response.RequestsToJoinCommunity()[0]
|
|
s.Require().NotNil(requestToJoin)
|
|
s.Require().Equal(community.ID(), requestToJoin.CommunityID)
|
|
s.Require().NotEmpty(requestToJoin.ID)
|
|
s.Require().NotEmpty(requestToJoin.Clock)
|
|
s.Require().Equal(requestToJoin.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin.State)
|
|
|
|
// Bobs receives the request to join and it's automatically accepted
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) > 0 && len(r.RequestsToJoinCommunity()) > 0
|
|
},
|
|
"message not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
// Alice receives the updated community description with channel information
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) > 0 && len(r.chats) > 0
|
|
},
|
|
"message not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
// Check whether community's general chat is available for Alice
|
|
_, exists := response.chats[generalChannelChatID]
|
|
s.Require().True(exists)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestJoinCommunity() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.CommunitiesSettings(), 1)
|
|
|
|
communitySettings := response.CommunitiesSettings()[0]
|
|
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_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat",
|
|
},
|
|
HideIfPermissionsNotMet: true,
|
|
}
|
|
response, err = s.bob.CreateCommunityChat(community.ID(), orgChat)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
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()))
|
|
s.Require().True(createdChat.HideIfPermissionsNotMet)
|
|
|
|
// 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
|
|
community = response.Communities()[0]
|
|
chats := community.Chats()
|
|
s.Require().Len(chats, 2)
|
|
|
|
// Send a community message
|
|
chat := CreateOneToOneChat(common.PubkeyToHex(&s.alice.identity.PublicKey), &s.alice.identity.PublicKey, s.bob.transport)
|
|
|
|
inputMessage := common.NewMessage()
|
|
inputMessage.ChatId = chat.ID
|
|
inputMessage.Text = "some text"
|
|
inputMessage.CommunityID = community.IDString()
|
|
|
|
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
|
|
}
|
|
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, 1)
|
|
s.Require().Len(response.Communities(), 1)
|
|
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(), false)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(response.Communities()[0].Joined())
|
|
s.Require().True(response.Communities()[0].JoinedAt() > 0)
|
|
s.Require().Len(response.Chats(), 2)
|
|
s.Require().Len(response.Communities()[0].Categories(), 1)
|
|
s.Require().Len(response.notifications, 1)
|
|
for _, notification := range response.notifications {
|
|
s.Require().Equal(notification.Title, community.Name())
|
|
s.Require().EqualValues(notification.BodyType, localnotifications.CategoryCommunityJoined)
|
|
s.Require().EqualValues(notification.Category, localnotifications.CategoryCommunityJoined)
|
|
}
|
|
|
|
var categoryID string
|
|
for k := range response.Communities()[0].Categories() {
|
|
categoryID = k
|
|
}
|
|
|
|
// The chat should be created
|
|
|
|
found := false
|
|
for _, createdChat := range response.Chats() {
|
|
if orgChat.Identity.DisplayName == createdChat.Name {
|
|
found = true
|
|
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()))
|
|
}
|
|
}
|
|
s.Require().True(found)
|
|
|
|
// Create another org chat
|
|
orgChat = &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core-ui",
|
|
Emoji: "👍",
|
|
Description: "status-core-ui community chat",
|
|
},
|
|
}
|
|
response, err = s.bob.CreateCommunityChat(community.ID(), orgChat)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
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
|
|
}
|
|
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
|
|
}
|
|
}
|
|
return errors.New("chat not found")
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
communities, err = s.alice.Communities()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(communities, 1)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().NotNil(actualChat)
|
|
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
|
|
response, err = s.alice.LeaveCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().False(response.Communities()[0].Joined())
|
|
s.Require().Len(response.RemovedChats(), 3)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) createCommunity() (*communities.Community, *Chat) {
|
|
return createCommunity(&s.Suite, s.owner)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) advertiseCommunityTo(community *communities.Community, owner *Messenger, user *Messenger) {
|
|
advertiseCommunityTo(&s.Suite, community, owner, user)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) joinCommunity(community *communities.Community, owner *Messenger, user *Messenger) {
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
joinCommunity(&s.Suite, community, owner, user, request, "")
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityContactCodeAdvertisement() {
|
|
// add bob's profile keypair
|
|
bobProfileKp := accounts.GetProfileKeypairForTest(true, false, false)
|
|
bobProfileKp.KeyUID = s.bob.account.KeyUID
|
|
bobProfileKp.Accounts[0].KeyUID = s.bob.account.KeyUID
|
|
|
|
err := s.bob.settings.SaveOrUpdateKeypair(bobProfileKp)
|
|
s.Require().NoError(err)
|
|
|
|
// create community and make bob and alice join to it
|
|
community, _ := s.createCommunity()
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.bob)
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.alice)
|
|
|
|
s.joinCommunity(community, s.owner, s.bob)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// Trigger ContactCodeAdvertisement
|
|
err = s.bob.SetDisplayName("bobby")
|
|
s.Require().NoError(err)
|
|
err = s.bob.SetBio("I like P2P chats")
|
|
s.Require().NoError(err)
|
|
|
|
// Ensure alice receives bob's ContactCodeAdvertisement
|
|
err = tt.RetryWithBackOff(func() error {
|
|
response, err := s.alice.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(response.Contacts) == 0 {
|
|
return errors.New("no contacts in response")
|
|
}
|
|
if response.Contacts[0].DisplayName != "bobby" {
|
|
return errors.New("display name was not updated")
|
|
}
|
|
if response.Contacts[0].Bio != "I like P2P chats" {
|
|
return errors.New("bio was not updated")
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestPostToCommunityChat() {
|
|
community, chat := s.createCommunity()
|
|
|
|
chatID := chat.ID
|
|
inputMessage := common.NewMessage()
|
|
inputMessage.ChatId = chatID
|
|
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
|
|
inputMessage.Text = "some text"
|
|
|
|
ctx := context.Background()
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
|
|
// Send message without even spectating fails
|
|
_, err := s.alice.SendChatMessage(ctx, inputMessage)
|
|
s.Require().Error(err)
|
|
|
|
// Sending a message without joining fails
|
|
_, err = s.alice.SpectateCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
_, err = s.alice.SendChatMessage(ctx, inputMessage)
|
|
s.Require().Error(err)
|
|
|
|
// Sending should work now
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
_, err = s.alice.SendChatMessage(ctx, inputMessage)
|
|
s.Require().NoError(err)
|
|
|
|
var response *MessengerResponse
|
|
// Pull message and make sure org is received
|
|
err = tt.RetryWithBackOff(func() error {
|
|
response, err = s.owner.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(response.messages) == 0 {
|
|
return errors.New("message not received")
|
|
}
|
|
return nil
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(inputMessage.Text, response.Messages()[0].Text)
|
|
s.Require().Equal(s.alice.account.GetCustomizationColor(), response.Contacts[0].CustomizationColor)
|
|
|
|
// check if response contains the chat we're interested in
|
|
// we use this instead of checking just the length of the chat because
|
|
// a CommunityDescription message might be received in the meantime due to syncing
|
|
// hence response.Chats() might contain the general chat, and the new chat;
|
|
// or only the new chat if the CommunityDescription message has not arrived
|
|
found := false
|
|
for _, chat := range response.Chats() {
|
|
if chat.ID == chatID {
|
|
found = true
|
|
}
|
|
}
|
|
s.Require().True(found)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestPinMessageInCommunityChat() {
|
|
ctx := context.Background()
|
|
|
|
// Create a community
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
PinMessageAllMembersEnabled: true,
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community := response.Communities()[0]
|
|
s.Require().NotNil(community)
|
|
s.Require().Equal(community.AllowsAllMembersToPinMessage(), true)
|
|
|
|
// Create a community chat
|
|
orgChat := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat",
|
|
},
|
|
}
|
|
response, err = s.owner.CreateCommunityChat(community.ID(), orgChat)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
chat := response.Chats()[0]
|
|
s.Require().NotNil(chat)
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.bob)
|
|
s.joinCommunity(community, s.owner, s.bob)
|
|
|
|
inputMessage := common.NewMessage()
|
|
inputMessage.ChatId = chat.ID
|
|
inputMessage.ContentType = protobuf.ChatMessage_TEXT_PLAIN
|
|
inputMessage.Text = "message to be pinned"
|
|
|
|
sendResponse, err := s.bob.SendChatMessage(ctx, inputMessage)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(sendResponse.Messages(), 1)
|
|
|
|
// bob should be able to pin the message
|
|
pinMessage := common.NewPinMessage()
|
|
pinMessage.ChatId = chat.ID
|
|
pinMessage.MessageId = inputMessage.ID
|
|
pinMessage.Pinned = true
|
|
sendResponse, err = s.bob.SendPinMessage(ctx, pinMessage)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(sendResponse.PinMessages(), 1)
|
|
|
|
// alice does not fully join the community,
|
|
// so she should not be able to send the pin message
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
response, err = s.alice.SpectateCommunity(community.ID())
|
|
s.Require().NotNil(response)
|
|
s.Require().NoError(err)
|
|
failedPinMessage := common.NewPinMessage()
|
|
failedPinMessage.ChatId = chat.ID
|
|
failedPinMessage.MessageId = inputMessage.ID
|
|
failedPinMessage.Pinned = true
|
|
sendResponse, err = s.alice.SendPinMessage(ctx, failedPinMessage)
|
|
s.Require().Nil(sendResponse)
|
|
s.Require().Error(err, "can't pin message")
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestImportCommunity() {
|
|
ctx := context.Background()
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
category := &requests.CreateCommunityCategory{
|
|
CommunityID: community.ID(),
|
|
CategoryName: "category-name",
|
|
ChatIDs: []string{},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityCategory(category)
|
|
s.Require().NoError(err)
|
|
community = response.Communities()[0]
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.bob)
|
|
s.joinCommunity(community, s.owner, s.bob)
|
|
|
|
privateKey, err := s.owner.ExportCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
_, err = s.alice.ImportCommunity(ctx, privateKey)
|
|
s.Require().NoError(err)
|
|
|
|
newDescription := "new description set post import"
|
|
_, err = s.alice.EditCommunity(&requests.EditCommunity{
|
|
CommunityID: community.ID(),
|
|
CreateCommunity: requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: community.Name(),
|
|
Color: community.Color(),
|
|
Description: newDescription,
|
|
},
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// bob receives new description
|
|
_, err = WaitOnMessengerResponse(s.bob, func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) > 0 && r.Communities()[0].DescriptionText() == newDescription
|
|
}, "new description not received")
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRemovePrivateKey() {
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community := response.Communities()[0]
|
|
s.Require().True(community.IsControlNode())
|
|
s.Require().True(community.IsControlNode())
|
|
|
|
response, err = s.bob.RemovePrivateKey(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community = response.Communities()[0]
|
|
s.Require().True(community.IsOwner())
|
|
s.Require().False(community.IsControlNode())
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRolesAfterImportCommunity() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create a community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.CommunitiesSettings(), 1)
|
|
s.Require().True(response.Communities()[0].Joined())
|
|
s.Require().True(response.Communities()[0].IsControlNode())
|
|
s.Require().True(response.Communities()[0].IsMemberOwner(&s.bob.identity.PublicKey))
|
|
s.Require().False(response.Communities()[0].IsMemberOwner(&s.alice.identity.PublicKey))
|
|
|
|
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)
|
|
s.Require().NoError(err)
|
|
community = response.Communities()[0]
|
|
|
|
privateKey, err := s.bob.ExportCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
response, err = s.alice.ImportCommunity(ctx, privateKey)
|
|
s.Require().NoError(err)
|
|
s.Require().True(response.Communities()[0].IsMemberOwner(&s.alice.identity.PublicKey))
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRequestAccess() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
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)
|
|
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)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification := response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
|
|
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(requestToJoin1.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
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, 1)
|
|
s.Require().Equal(allCommunities[0].ID(), community.ID())
|
|
s.Require().Equal(allCommunities[0].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(requestToJoin2.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin2.State)
|
|
|
|
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
s.Require().Equal(notification.Read, false)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
|
|
// 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))
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusAccepted)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Accepted, true)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
|
|
// 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.RequestsToJoinCommunity(), 1)
|
|
s.Require().Equal(communities.RequestToJoinStateAccepted, response.RequestsToJoinCommunity()[0].State)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusAccepted)
|
|
s.Require().Equal(notification.Read, false)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
|
|
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) TestDeletePendingRequestAccess() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Bob creates a community
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
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()
|
|
|
|
// Bob sends the community link to Alice
|
|
response, err = s.bob.SendChatMessage(ctx, message)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
// Retrieve community link & community for Alice
|
|
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)
|
|
|
|
// Alice request to join community
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
response, err = s.alice.RequestToJoinCommunity(request)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
requestToJoin := response.RequestsToJoinCommunity()[0]
|
|
s.Require().NotNil(requestToJoin)
|
|
s.Require().Equal(community.ID(), requestToJoin.CommunityID)
|
|
s.Require().NotEmpty(requestToJoin.ID)
|
|
s.Require().NotEmpty(requestToJoin.Clock)
|
|
s.Require().Equal(requestToJoin.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
|
|
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin.State)
|
|
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin.Clock)
|
|
|
|
// updating request clock by 8 days back
|
|
requestTime := uint64(time.Now().AddDate(0, 0, -8).Unix())
|
|
err = s.alice.communitiesManager.UpdateClockInRequestToJoin(requestToJoin.ID, requestTime)
|
|
s.Require().NoError(err)
|
|
|
|
// pull to make sure it has been saved
|
|
requestsToJoin, err := s.alice.MyPendingRequestsToJoin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
requestToJoin = requestsToJoin[0]
|
|
s.Require().Equal(requestToJoin.Clock, requestTime)
|
|
|
|
// 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
|
|
bobRetrieveAll := func() (*MessengerResponse, error) {
|
|
return s.bob.RetrieveAll()
|
|
}
|
|
err = tt.RetryWithBackOff(func() error {
|
|
response, err = bobRetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(response.RequestsToJoinCommunity()) == 0 {
|
|
return errors.New("request to join community not received")
|
|
}
|
|
|
|
// updating request clock by 8 days back
|
|
requestToJoin := response.RequestsToJoinCommunity()[0]
|
|
err = s.bob.communitiesManager.UpdateClockInRequestToJoin(requestToJoin.ID, requestTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(response.ActivityCenterNotifications()) == 0 {
|
|
return errors.New("request to join community notification not added in activity center")
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
// Check activity center notification for Bob
|
|
fetchActivityCenterNotificationsForAdmin := func() (*ActivityCenterPaginationResponse, error) {
|
|
return s.bob.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
})
|
|
}
|
|
notifications, err := fetchActivityCenterNotificationsForAdmin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 1)
|
|
|
|
notification := notifications.Notifications[0]
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
|
|
// Delete pending request to join
|
|
response, err = s.alice.CheckAndDeletePendingRequestToJoinCommunity(ctx, true)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
requestToJoin = response.RequestsToJoinCommunity()[0]
|
|
s.Require().True(requestToJoin.Deleted)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusIdle)
|
|
|
|
response, err = s.bob.CheckAndDeletePendingRequestToJoinCommunity(ctx, true)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
requestToJoin = response.RequestsToJoinCommunity()[0]
|
|
s.Require().True(requestToJoin.Deleted)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().True(notification.Deleted)
|
|
|
|
// Alice request to join community
|
|
request = &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
response, err = s.alice.RequestToJoinCommunity(request)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
aliceRequestToJoin := response.RequestsToJoinCommunity()[0]
|
|
|
|
// Retrieve request to join and Check activity center notification for Bob
|
|
err = tt.RetryWithBackOff(func() error {
|
|
response, err = bobRetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// NOTE: we might receive multiple requests to join in case of re-transmissions
|
|
// because request to join are hard deleted from the database, we can't check
|
|
// whether that's an old one or a new one. So here we test for the specific id
|
|
|
|
for _, r := range response.RequestsToJoinCommunity() {
|
|
if bytes.Equal(r.ID, aliceRequestToJoin.ID) {
|
|
return nil
|
|
}
|
|
}
|
|
return errors.New("request to join not found")
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Check activity center notification for Bob
|
|
notifications, err = fetchActivityCenterNotificationsForAdmin()
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 1)
|
|
|
|
notification = notifications.Notifications[0]
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestDeletePendingRequestAccessWithDeclinedState() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Bob creates a community
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
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()
|
|
|
|
// Bob sends the community link to Alice
|
|
response, err = s.bob.SendChatMessage(ctx, message)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
// Retrieve community link & community for Alice
|
|
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)
|
|
|
|
// Alice request to join community
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
response, err = s.alice.RequestToJoinCommunity(request)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
notification := response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().NotEmpty(notification.ID)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
s.Require().Equal(notification.Deleted, false)
|
|
s.Require().Equal(notification.Read, true)
|
|
|
|
requestToJoin := response.RequestsToJoinCommunity()[0]
|
|
s.Require().NotNil(requestToJoin)
|
|
s.Require().Equal(community.ID(), requestToJoin.CommunityID)
|
|
s.Require().NotEmpty(requestToJoin.ID)
|
|
s.Require().NotEmpty(requestToJoin.Clock)
|
|
s.Require().Equal(requestToJoin.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
|
|
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin.State)
|
|
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Equal(response.Communities()[0].RequestedToJoinAt(), requestToJoin.Clock)
|
|
|
|
// Alice deletes activity center notification
|
|
var updatedAt uint64 = 99
|
|
_, err = s.alice.MarkActivityCenterNotificationsDeleted(ctx, []types.HexBytes{notification.ID}, updatedAt, true)
|
|
s.Require().NoError(err)
|
|
|
|
// Check activity center notification for Bob after deleting
|
|
notifications, err := s.alice.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 0)
|
|
|
|
// updating request clock by 8 days back
|
|
requestTime := uint64(time.Now().AddDate(0, 0, -8).Unix())
|
|
err = s.alice.communitiesManager.UpdateClockInRequestToJoin(requestToJoin.ID, requestTime)
|
|
s.Require().NoError(err)
|
|
|
|
// pull to make sure it has been saved
|
|
requestsToJoin, err := s.alice.MyPendingRequestsToJoin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
requestToJoin = requestsToJoin[0]
|
|
s.Require().Equal(requestToJoin.Clock, requestTime)
|
|
|
|
// 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)
|
|
|
|
bobRetrieveAll := func() (*MessengerResponse, error) {
|
|
return s.bob.RetrieveAll()
|
|
}
|
|
|
|
// Retrieve request to join
|
|
err = tt.RetryWithBackOff(func() error {
|
|
response, err = bobRetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(response.RequestsToJoinCommunity()) == 0 {
|
|
return errors.New("request to join community not received")
|
|
}
|
|
|
|
// updating request clock by 8 days back
|
|
requestToJoin := response.RequestsToJoinCommunity()[0]
|
|
err = s.bob.communitiesManager.UpdateClockInRequestToJoin(requestToJoin.ID, requestTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(response.ActivityCenterNotifications()) == 0 {
|
|
return errors.New("request to join community notification not added in activity center")
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
// Check activity center notification for Bob
|
|
fetchActivityCenterNotificationsForAdmin := func() (*ActivityCenterPaginationResponse, error) {
|
|
return s.bob.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
})
|
|
}
|
|
|
|
notifications, err = fetchActivityCenterNotificationsForAdmin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 1)
|
|
|
|
notification = notifications.Notifications[0]
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
|
|
// Check if admin sees requests correctly
|
|
requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
requestsToJoin, err = s.bob.DeclinedRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 0)
|
|
|
|
// Decline request
|
|
declinedRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: requestToJoin.ID}
|
|
response, err = s.bob.DeclineRequestToJoinCommunity(declinedRequestToJoin)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusDeclined)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
s.Require().Equal(notification.Dismissed, true)
|
|
|
|
// Check if admin sees requests correctly
|
|
requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 0)
|
|
|
|
requestsToJoin, err = s.bob.DeclinedRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
// Bob deletes activity center notification
|
|
updatedAt++
|
|
_, err = s.bob.MarkActivityCenterNotificationsDeleted(ctx, []types.HexBytes{notification.ID}, updatedAt, true)
|
|
s.Require().NoError(err)
|
|
|
|
// Check activity center notification for Bob after deleting
|
|
notifications, err = fetchActivityCenterNotificationsForAdmin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 0)
|
|
|
|
// Delete pending request to join
|
|
response, err = s.alice.CheckAndDeletePendingRequestToJoinCommunity(ctx, true)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
requestToJoin = response.RequestsToJoinCommunity()[0]
|
|
s.Require().True(requestToJoin.Deleted)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusIdle)
|
|
s.Require().Equal(notification.Read, false)
|
|
s.Require().Equal(notification.Deleted, false)
|
|
|
|
notificationState := response.ActivityCenterState()
|
|
s.Require().False(notificationState.HasSeen)
|
|
|
|
// Alice request to join community
|
|
request = &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
response, err = s.alice.RequestToJoinCommunity(request)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
// Retrieve request to join and Check activity center notification for Bob
|
|
err = tt.RetryWithBackOff(func() error {
|
|
response, err = bobRetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(response.RequestsToJoinCommunity()) == 0 {
|
|
return errors.New("request to join community not received")
|
|
}
|
|
|
|
if len(response.ActivityCenterNotifications()) == 0 {
|
|
return errors.New("request to join community notification not added in activity center")
|
|
}
|
|
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
// Check activity center notification for Bob
|
|
notifications, err = fetchActivityCenterNotificationsForAdmin()
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 1)
|
|
|
|
notification = notifications.Notifications[0]
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
s.Require().False(notification.Deleted)
|
|
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCancelRequestAccess() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
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)
|
|
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(requestToJoin1.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
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, 1)
|
|
s.Require().Equal(allCommunities[0].ID(), community.ID())
|
|
s.Require().Equal(allCommunities[0].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")
|
|
}
|
|
if len(response.ActivityCenterNotifications()) == 0 {
|
|
return errors.New("request to join community notification not added in activity center")
|
|
}
|
|
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(requestToJoin2.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin2.State)
|
|
|
|
s.Require().Equal(requestToJoin1.ID, requestToJoin2.ID)
|
|
|
|
// Cancel request to join community
|
|
requestsToJoin, err = s.alice.MyPendingRequestsToJoin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
requestToJoin := requestsToJoin[0]
|
|
|
|
requestToCancel := &requests.CancelRequestToJoinCommunity{ID: requestToJoin.ID}
|
|
response, err = s.alice.CancelRequestToJoinCommunity(ctx, requestToCancel)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
s.Require().Equal(communities.RequestToJoinStateCanceled, response.RequestsToJoinCommunity()[0].State)
|
|
|
|
// pull to make sure it has been saved
|
|
cancelRequestsToJoin, err := s.alice.MyCanceledRequestsToJoin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(cancelRequestsToJoin, 1)
|
|
s.Require().Equal(cancelRequestsToJoin[0].State, communities.RequestToJoinStateCanceled)
|
|
|
|
// Retrieve cancel 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)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
// Retrieve activity center notifications for admin to make sure the request notification is deleted
|
|
notifications, err := s.bob.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 0)
|
|
|
|
cancelRequestToJoin2 := response.RequestsToJoinCommunity()[0]
|
|
|
|
s.Require().NotNil(cancelRequestToJoin2)
|
|
s.Require().Equal(community.ID(), cancelRequestToJoin2.CommunityID)
|
|
s.Require().False(cancelRequestToJoin2.Our)
|
|
s.Require().NotEmpty(cancelRequestToJoin2.ID)
|
|
s.Require().NotEmpty(cancelRequestToJoin2.Clock)
|
|
s.Require().Equal(cancelRequestToJoin2.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
|
|
s.Require().Equal(cancelRequestToJoin2.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
s.Require().Equal(communities.RequestToJoinStateCanceled, cancelRequestToJoin2.State)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRequestAccessAgain() {
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community := response.Communities()[0]
|
|
|
|
s.advertiseCommunityTo(community, s.bob, s.alice)
|
|
|
|
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)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification := response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
|
|
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, 1)
|
|
s.Require().Equal(allCommunities[0].ID(), community.ID())
|
|
s.Require().Equal(allCommunities[0].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")
|
|
|
|
// 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.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusAccepted)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Accepted, true)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
|
|
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")
|
|
}
|
|
if len(response.ActivityCenterNotifications()) == 0 {
|
|
return errors.New("activity center notification not received")
|
|
}
|
|
if response.ActivityCenterState().HasSeen {
|
|
return errors.New("activity center seen state is incorrect")
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// Check we got AC notification for Alice
|
|
aliceNotifications, err := s.alice.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{ActivityCenterNotificationTypeCommunityKicked},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
},
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(aliceNotifications.Notifications, 1)
|
|
s.Require().Equal(community.IDString(), aliceNotifications.Notifications[0].CommunityID)
|
|
|
|
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)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
|
|
// Retrieve request to join
|
|
response, err = WaitOnMessengerResponse(s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.RequestsToJoinCommunity()) == 1
|
|
},
|
|
"request to join community was never 1",
|
|
)
|
|
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) TestDeclineAccess() {
|
|
ctx := context.Background()
|
|
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
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)
|
|
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)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification := response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Dismissed, false)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
|
|
// 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, 1)
|
|
s.Require().Equal(allCommunities[0].ID(), community.ID())
|
|
s.Require().Equal(allCommunities[0].RequestedToJoinAt(), requestToJoin1.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)
|
|
|
|
// Check if admin sees requests correctly
|
|
requestsToJoin, err := s.bob.PendingRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
requestsToJoin, err = s.bob.DeclinedRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 0)
|
|
|
|
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)
|
|
|
|
// Decline request
|
|
declinedRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: requestToJoin1.ID}
|
|
response, err = s.bob.DeclineRequestToJoinCommunity(declinedRequestToJoin)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusDeclined)
|
|
s.Require().Equal(notification.Read, true)
|
|
s.Require().Equal(notification.Accepted, false)
|
|
s.Require().Equal(notification.Dismissed, true)
|
|
|
|
// Check if admin sees requests correctly
|
|
requestsToJoin, err = s.bob.PendingRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 0)
|
|
|
|
requestsToJoin, err = s.bob.DeclinedRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(requestsToJoin, 1)
|
|
|
|
// Accept declined 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))
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification = response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityMembershipRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusAccepted)
|
|
|
|
// 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.bob.DeclinedRequestsToJoinForCommunity(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) TestLeaveAndRejoinCommunity() {
|
|
community, _ := s.createCommunity()
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.alice)
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.bob)
|
|
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.bob)
|
|
|
|
joinedCommunities, err := s.owner.communitiesManager.Joined()
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(3, joinedCommunities[0].MembersCount())
|
|
|
|
response, err := s.alice.LeaveCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().False(response.Communities()[0].Joined())
|
|
|
|
// admin should receive alice's request to leave
|
|
// and then update and advertise community members list accordingly
|
|
|
|
verifyCommunityMembers := func(user *Messenger) error {
|
|
response, err := user.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(response.Communities()) == 0 {
|
|
return errors.New("no communities in response")
|
|
}
|
|
|
|
var communityMembersError error = nil
|
|
|
|
if response.Communities()[0].MembersCount() != 2 {
|
|
communityMembersError = fmt.Errorf("invalid number of members: %d", response.Communities()[0].MembersCount())
|
|
} else if !response.Communities()[0].HasMember(&s.owner.identity.PublicKey) {
|
|
communityMembersError = errors.New("admin removed from community")
|
|
} else if !response.Communities()[0].HasMember(&s.bob.identity.PublicKey) {
|
|
communityMembersError = errors.New("bob removed from community")
|
|
} else if response.Communities()[0].HasMember(&s.alice.identity.PublicKey) {
|
|
communityMembersError = errors.New("alice not removed from community")
|
|
}
|
|
|
|
return communityMembersError
|
|
}
|
|
err = tt.RetryWithBackOff(func() error {
|
|
return verifyCommunityMembers(s.owner)
|
|
})
|
|
s.Require().NoError(err)
|
|
err = tt.RetryWithBackOff(func() error {
|
|
return verifyCommunityMembers(s.bob)
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
joinedCommunities, err = s.owner.communitiesManager.Joined()
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(2, joinedCommunities[0].MembersCount())
|
|
|
|
chats, err := s.alice.persistence.Chats()
|
|
s.Require().NoError(err)
|
|
var numberInactiveChats = 0
|
|
for i := 0; i < len(chats); i++ {
|
|
if !chats[i].Active {
|
|
numberInactiveChats++
|
|
}
|
|
}
|
|
s.Require().Equal(2, numberInactiveChats)
|
|
|
|
// alice can rejoin
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
joinedCommunities, err = s.owner.communitiesManager.Joined()
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(3, joinedCommunities[0].MembersCount())
|
|
|
|
chats, err = s.alice.persistence.Chats()
|
|
s.Require().NoError(err)
|
|
numberInactiveChats = 0
|
|
for i := 0; i < len(chats); i++ {
|
|
if !chats[i].Active {
|
|
numberInactiveChats++
|
|
}
|
|
}
|
|
s.Require().Equal(1, numberInactiveChats)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestShareCommunity() {
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "status",
|
|
Description: "status community description",
|
|
Color: "#FFFFFF",
|
|
Image: "../_assets/tests/status.png",
|
|
ImageAx: 0,
|
|
ImageAy: 0,
|
|
ImageBx: 256,
|
|
ImageBy: 256,
|
|
Banner: images.CroppedImage{
|
|
ImagePath: "../_assets/tests/IMG_1205.HEIC.jpg",
|
|
X: 0,
|
|
Y: 0,
|
|
Width: 160,
|
|
Height: 90,
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunity(description, true)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
community := response.Communities()[0]
|
|
|
|
inputMessageText := "Come on alice, You'll like it here!"
|
|
// Alice shares community with Bob
|
|
response, err = s.owner.ShareCommunity(&requests.ShareCommunity{
|
|
CommunityID: community.ID(),
|
|
Users: []types.HexBytes{common.PubkeyToHexBytes(&s.alice.identity.PublicKey)},
|
|
InviteMessage: inputMessageText,
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Messages(), 1)
|
|
sentMessageText := response.Messages()[0].Text
|
|
|
|
_, err = WaitOnMessengerResponse(s.alice, func(r *MessengerResponse) bool {
|
|
return len(r.Messages()) > 0
|
|
}, "Messages not received")
|
|
|
|
communityURL := response.Messages()[0].UnfurledStatusLinks.GetUnfurledStatusLinks()[0].Url
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(fmt.Sprintf("%s\n%s", inputMessageText, communityURL), sentMessageText)
|
|
s.Require().NotNil(response.Messages()[0].UnfurledStatusLinks.GetUnfurledStatusLinks()[0].GetCommunity().CommunityId)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestShareCommunityWithPreviousMember() {
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
}
|
|
|
|
// Create an community chat
|
|
response, err := s.bob.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community := response.Communities()[0]
|
|
|
|
orgChat := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat",
|
|
},
|
|
}
|
|
response, err = s.bob.CreateCommunityChat(community.ID(), orgChat)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
community = response.Communities()[0]
|
|
communityChat := response.Chats()[0]
|
|
|
|
// Add Alice to the community before sharing it
|
|
_, err = community.AddMember(&s.alice.identity.PublicKey, []protobuf.CommunityMember_Roles{}, community.Clock())
|
|
s.Require().NoError(err)
|
|
|
|
err = s.bob.communitiesManager.SaveCommunity(community)
|
|
s.Require().NoError(err)
|
|
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.bob, s.alice)
|
|
|
|
// Add bob to contacts so it does not go on activity center
|
|
bobPk := common.PubkeyToHex(&s.bob.identity.PublicKey)
|
|
request := &requests.AddContact{ID: bobPk}
|
|
_, err = s.alice.AddContact(context.Background(), request)
|
|
s.Require().NoError(err)
|
|
|
|
// Alice should have the Joined status for the community
|
|
communityInResponse := response.Communities()[0]
|
|
s.Require().Equal(community.ID(), communityInResponse.ID())
|
|
s.Require().True(communityInResponse.Joined())
|
|
|
|
// Alice is able to receive messages in the community
|
|
inputMessage := buildTestMessage(*communityChat)
|
|
sendResponse, err := s.bob.SendChatMessage(context.Background(), inputMessage)
|
|
messageID := sendResponse.Messages()[0].ID
|
|
s.NoError(err)
|
|
s.Require().Len(sendResponse.Messages(), 1)
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool { return len(r.messages) > 0 },
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(messageID, response.Messages()[0].ID)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestBanUser() {
|
|
community, _ := s.createCommunity()
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
response, err := s.owner.BanUserFromCommunity(
|
|
context.Background(),
|
|
&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))
|
|
s.Require().Len(community.PendingAndBannedMembers(), 1)
|
|
s.Require().Equal(community.PendingAndBannedMembers()[s.alice.IdentityPublicKeyString()], communities.CommunityMemberBanned)
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) == 1 &&
|
|
len(r.Communities()[0].PendingAndBannedMembers()) == 1 &&
|
|
community.PendingAndBannedMembers()[s.alice.IdentityPublicKeyString()] == communities.CommunityMemberBanned &&
|
|
r.Communities()[0].IsBanned(&s.alice.identity.PublicKey) &&
|
|
len(r.ActivityCenterNotifications()) == 1 &&
|
|
!r.ActivityCenterState().HasSeen &&
|
|
!r.Communities()[0].Spectated() &&
|
|
!r.Communities()[0].Joined()
|
|
|
|
},
|
|
"no message about alice ban",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
|
|
// Check we got ban AC notification for Alice
|
|
aliceNotifications, err := s.alice.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{ActivityCenterNotificationTypeCommunityBanned},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
},
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(aliceNotifications.Notifications, 1)
|
|
s.Require().Equal(community.IDString(), aliceNotifications.Notifications[0].CommunityID)
|
|
|
|
response, err = s.owner.UnbanUserFromCommunity(
|
|
&requests.UnbanUserFromCommunity{
|
|
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().False(community.IsBanned(&s.alice.identity.PublicKey))
|
|
s.Require().Len(community.PendingAndBannedMembers(), 0)
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) == 1 &&
|
|
len(r.Communities()[0].PendingAndBannedMembers()) == 0 &&
|
|
!r.Communities()[0].IsBanned(&s.alice.identity.PublicKey) &&
|
|
len(r.ActivityCenterNotifications()) == 1 && !r.ActivityCenterState().HasSeen
|
|
},
|
|
"no message about alice unban",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().False(response.Communities()[0].Joined())
|
|
|
|
// Check we got unban AC notification for Alice
|
|
aliceNotifications, err = s.alice.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{ActivityCenterNotificationTypeCommunityUnbanned},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
},
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(aliceNotifications.Notifications, 1)
|
|
s.Require().Equal(community.IDString(), aliceNotifications.Notifications[0].CommunityID)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) createOtherDevice(m1 *Messenger) *Messenger {
|
|
m2 := s.newMessengerWithKey(m1.identity)
|
|
|
|
tcs, err := m2.communitiesManager.All()
|
|
s.Require().NoError(err, "m2.communitiesManager.All")
|
|
s.Len(tcs, 0, "Must have 0 communities")
|
|
|
|
// Pair devices
|
|
metadata := &multidevice.InstallationMetadata{
|
|
Name: "other-device",
|
|
DeviceType: "other-device-type",
|
|
}
|
|
err = m2.SetInstallationMetadata(m2.installationID, metadata)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = m2.Start()
|
|
s.Require().NoError(err)
|
|
|
|
return m2
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunitySettings() {
|
|
// Create new device
|
|
alicesOtherDevice := s.createOtherDevice(s.alice)
|
|
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
|
|
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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 community settings
|
|
cs, err := s.alice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
s.Require().NoError(err, "communitiesManager.GetCommunitySettingsByID")
|
|
s.NotNil(cs, "Must have community settings")
|
|
|
|
// Wait for the message to reach its destination
|
|
err = tt.RetryWithBackOff(func() error {
|
|
_, err = alicesOtherDevice.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Do we have new synced community settings?
|
|
syncedSettings, err := alicesOtherDevice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
if err != nil || syncedSettings == nil {
|
|
return fmt.Errorf("community with sync not received %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
tcs, err := alicesOtherDevice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// Check the community settings on their device matched the community settings on Alice's device
|
|
s.Equal(cs.CommunityID, tcs.CommunityID)
|
|
s.Equal(cs.HistoryArchiveSupportEnabled, tcs.HistoryArchiveSupportEnabled)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunitySettings_EditCommunity() {
|
|
// Create new device
|
|
alicesOtherDevice := s.createOtherDevice(s.alice)
|
|
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
|
|
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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 community settings
|
|
cs, err := s.alice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
s.Require().NoError(err, "communitiesManager.GetCommunitySettingsByID")
|
|
s.NotNil(cs, "Must have community settings")
|
|
|
|
// Wait for the message to reach its destination
|
|
err = tt.RetryWithBackOff(func() error {
|
|
_, err = alicesOtherDevice.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Do we have new synced community settings?
|
|
syncedSettings, err := alicesOtherDevice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
if err != nil || syncedSettings == nil {
|
|
return fmt.Errorf("community settings with sync not received %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
tcs, err := alicesOtherDevice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// Check the community settings on their device matched the community settings on Alice's device
|
|
s.Equal(cs.CommunityID, tcs.CommunityID)
|
|
s.Equal(cs.HistoryArchiveSupportEnabled, tcs.HistoryArchiveSupportEnabled)
|
|
|
|
req := createCommunityReq
|
|
req.HistoryArchiveSupportEnabled = true
|
|
editCommunityReq := &requests.EditCommunity{
|
|
CommunityID: newCommunity.ID(),
|
|
CreateCommunity: *req,
|
|
}
|
|
|
|
mr, err = s.alice.EditCommunity(editCommunityReq)
|
|
s.Require().NoError(err, "s.alice.EditCommunity")
|
|
var editedCommunity *communities.Community
|
|
for _, com := range mr.Communities() {
|
|
if com.Name() == createCommunityReq.Name {
|
|
editedCommunity = com
|
|
}
|
|
}
|
|
s.Require().NotNil(editedCommunity)
|
|
|
|
// Wait a bit for sync messages to reach destination
|
|
time.Sleep(1 * time.Second)
|
|
err = tt.RetryWithBackOff(func() error {
|
|
_, err = alicesOtherDevice.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
tcs, err = alicesOtherDevice.communitiesManager.GetCommunitySettingsByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// Check the community settings on their device matched the community settings on Alice's device
|
|
s.Equal(cs.CommunityID, tcs.CommunityID)
|
|
s.Equal(req.HistoryArchiveSupportEnabled, tcs.HistoryArchiveSupportEnabled)
|
|
}
|
|
|
|
// TestSyncCommunity tests basic sync functionality between 2 Messengers
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunity() {
|
|
|
|
// Create new device
|
|
alicesOtherDevice := s.createOtherDevice(s.alice)
|
|
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
|
|
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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 1 community
|
|
cs, err := s.alice.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(cs, 1, "Must have 1 community")
|
|
|
|
// 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, 1, "There must be 1 community")
|
|
|
|
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())
|
|
|
|
// Private Key for synced community should be null
|
|
s.Require().NotNil(newCommunity.PrivateKey())
|
|
s.Require().Nil(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.Spectated(), tnc.Spectated())
|
|
|
|
s.True(newCommunity.IsControlNode())
|
|
s.True(newCommunity.IsOwner())
|
|
|
|
// Even though synced device have the private key, it is not the control node
|
|
// There can be only one control node
|
|
s.False(tnc.IsControlNode())
|
|
s.True(tnc.IsOwner())
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunity_EncryptionKeys() {
|
|
// Create new device
|
|
ownersOtherDevice := s.createOtherDevice(s.owner)
|
|
defer TearDownMessenger(&s.Suite, ownersOtherDevice)
|
|
|
|
PairDevices(&s.Suite, ownersOtherDevice, s.owner)
|
|
|
|
community, chat := s.createCommunity()
|
|
s.owner.communitiesManager.RekeyInterval = 1 * time.Hour
|
|
|
|
{ // ensure both community and channel are encrypted
|
|
permissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
_, err := s.owner.CreateCommunityTokenPermission(&permissionRequest)
|
|
s.Require().NoError(err)
|
|
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
ChatIds: []string{chat.ID},
|
|
}
|
|
|
|
_, err = s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
getKeysCount := func(m *Messenger) (communityKeysCount int, channelKeysCount int) {
|
|
keys, err := m.encryptor.GetAllHRKeys(community.ID())
|
|
s.Require().NoError(err)
|
|
if keys != nil {
|
|
communityKeysCount = len(keys.Keys)
|
|
}
|
|
|
|
channelKeys, err := m.encryptor.GetAllHRKeys([]byte(community.IDString() + chat.CommunityChatID()))
|
|
s.Require().NoError(err)
|
|
if channelKeys != nil {
|
|
channelKeysCount = len(channelKeys.Keys)
|
|
}
|
|
return
|
|
}
|
|
|
|
communityKeysCount, channelKeysCount := getKeysCount(s.owner)
|
|
s.Require().GreaterOrEqual(communityKeysCount, 1)
|
|
s.Require().GreaterOrEqual(channelKeysCount, 1)
|
|
|
|
// ensure both community and channel keys are synced
|
|
_, err := WaitOnMessengerResponse(ownersOtherDevice, func(mr *MessengerResponse) bool {
|
|
communityKeysCount, channelKeysCount := getKeysCount(s.owner)
|
|
syncedCommunityKeysCount, syncedChannelKeysCount := getKeysCount(ownersOtherDevice)
|
|
|
|
return communityKeysCount == syncedCommunityKeysCount && channelKeysCount == syncedChannelKeysCount
|
|
}, "keys not synced")
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
// 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 := s.createOtherDevice(s.alice)
|
|
|
|
// Pair alice's two devices
|
|
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
|
|
PairDevices(&s.Suite, s.alice, alicesOtherDevice)
|
|
|
|
// Check bob the admin has 0 community
|
|
tcs2, err := s.bob.communitiesManager.All()
|
|
s.Require().NoError(err, "admin.communitiesManager.All")
|
|
s.Len(tcs2, 0, "Must have 0 communities")
|
|
|
|
// Bob the admin creates a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
mr, err := s.bob.CreateCommunity(createCommunityReq, true)
|
|
s.Require().NoError(err, "CreateCommunity")
|
|
s.Require().NotNil(mr)
|
|
s.Len(mr.Communities(), 1)
|
|
|
|
community := mr.Communities()[0]
|
|
|
|
// Check that admin has 1 community
|
|
acs, err := s.bob.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(acs, 1, "Must have 1 communities")
|
|
|
|
// Check that Alice has 0 communities on either device
|
|
cs, err := s.alice.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(cs, 0, "Must have 0 communities")
|
|
|
|
tcs1, err := alicesOtherDevice.communitiesManager.All()
|
|
s.Require().NoError(err, "alicesOtherDevice.communitiesManager.All")
|
|
s.Len(tcs1, 0, "Must have 0 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 1 community
|
|
cs, err = s.alice.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(cs, 1, "Must have 1 community")
|
|
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.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
|
|
notification := response.ActivityCenterNotifications()[0]
|
|
s.Require().NotNil(notification)
|
|
s.Require().Equal(notification.Type, ActivityCenterNotificationTypeCommunityRequest)
|
|
s.Require().Equal(notification.MembershipStatus, ActivityCenterMembershipStatusPending)
|
|
|
|
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(aRtj.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
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, 1)
|
|
s.Require().Equal(allCommunities[0].ID(), community.ID())
|
|
s.Require().Equal(allCommunities[0].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)
|
|
s.Equal(aRtj.CustomizationColor, aodRtj.CustomizationColor)
|
|
|
|
// 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(bobRtj.CustomizationColor, s.alice.account.GetCustomizationColor())
|
|
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)
|
|
s.Equal(aRtj.CustomizationColor, bobRtj.CustomizationColor)
|
|
}
|
|
|
|
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 := s.createOtherDevice(s.alice)
|
|
|
|
// Pair alice's two devices
|
|
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
|
|
PairDevices(&s.Suite, s.alice, alicesOtherDevice)
|
|
|
|
// Check bob the admin has only zero community
|
|
tcs2, err := s.bob.communitiesManager.All()
|
|
s.Require().NoError(err, "admin.communitiesManager.All")
|
|
s.Len(tcs2, 0, "Must have 0 communities")
|
|
|
|
// Bob the admin creates a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
mr, err := s.bob.CreateCommunity(createCommunityReq, true)
|
|
s.Require().NoError(err, "CreateCommunity")
|
|
s.Require().NotNil(mr)
|
|
s.Len(mr.Communities(), 1)
|
|
|
|
community := mr.Communities()[0]
|
|
|
|
// Check that admin has 1 community
|
|
acs, err := s.bob.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(acs, 1, "Must have 1 community")
|
|
|
|
// Check that Alice has 0 community on either device
|
|
cs, err := s.alice.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(cs, 0, "Must have 0 communities")
|
|
|
|
tcs1, err := alicesOtherDevice.communitiesManager.All()
|
|
s.Require().NoError(err, "alicesOtherDevice.communitiesManager.All")
|
|
s.Len(tcs1, 0, "Must have 0 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 1 community
|
|
cs, err = s.alice.communitiesManager.All()
|
|
s.Require().NoError(err, "communitiesManager.All")
|
|
s.Len(cs, 1, "Must have 1 community")
|
|
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(), false)
|
|
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)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunity_ImportCommunity() {
|
|
// Owner creates community
|
|
community, _ := s.createCommunity()
|
|
s.Require().True(community.IsControlNode())
|
|
|
|
// New device is created & paired
|
|
ownersOtherDevice := s.createOtherDevice(s.owner)
|
|
PairDevices(&s.Suite, ownersOtherDevice, s.owner)
|
|
PairDevices(&s.Suite, s.owner, ownersOtherDevice)
|
|
|
|
privateKey, err := s.owner.ExportCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// New device imports the community (before it is received via sync message)
|
|
ctx := context.Background()
|
|
response, err := ownersOtherDevice.ImportCommunity(ctx, privateKey)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Equal(community.IDString(), response.Communities()[0].IDString())
|
|
// New device becomes the control node
|
|
s.Require().True(response.Communities()[0].IsControlNode())
|
|
|
|
// Old device is no longer the control node
|
|
_, err = WaitOnMessengerResponse(s.owner, func(response *MessengerResponse) bool {
|
|
if len(response.Communities()) != 1 {
|
|
return false
|
|
}
|
|
c := response.Communities()[0]
|
|
return c.IDString() == community.IDString() && !c.IsControlNode()
|
|
}, "community not synced")
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunity_OutdatedDescription() {
|
|
community, _ := s.createCommunity()
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
|
|
// Spectate community
|
|
_, err := s.alice.SpectateCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// Update alice's community reference
|
|
community, err = s.alice.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(community.Joined())
|
|
s.Require().True(community.Spectated())
|
|
|
|
// Create sync message for later
|
|
syncCommunityMsg, err := s.alice.buildSyncInstallationCommunity(community, 1)
|
|
s.Require().NoError(err)
|
|
s.Require().False(syncCommunityMsg.Joined)
|
|
s.Require().True(syncCommunityMsg.Spectated)
|
|
|
|
// Join community
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// Update owner's community reference
|
|
community, err = s.owner.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.HasMember(s.alice.IdentityPublicKey()))
|
|
|
|
// Create another device
|
|
aliceOtherDevice := s.createOtherDevice(s.alice)
|
|
defer TearDownMessenger(&s.Suite, aliceOtherDevice)
|
|
|
|
// Make other device receive community
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, aliceOtherDevice)
|
|
community, err = aliceOtherDevice.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.Joined())
|
|
|
|
// Then make other device handle sync message with outdated community description
|
|
messageState := aliceOtherDevice.buildMessageState()
|
|
err = aliceOtherDevice.handleSyncInstallationCommunity(messageState, syncCommunityMsg)
|
|
s.Require().NoError(err)
|
|
|
|
// Then community should not be left
|
|
community, err = aliceOtherDevice.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.Joined())
|
|
s.Require().False(community.Spectated())
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSetMutePropertyOnChatsByCategory() {
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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)
|
|
|
|
orgChat1 := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat",
|
|
},
|
|
}
|
|
|
|
orgChat2 := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core2",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat2",
|
|
},
|
|
}
|
|
|
|
mr, err = s.alice.CreateCommunityChat(newCommunity.ID(), orgChat1)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(mr)
|
|
s.Require().Len(mr.Communities(), 1)
|
|
s.Require().Len(mr.Chats(), 1)
|
|
|
|
mr, err = s.alice.CreateCommunityChat(newCommunity.ID(), orgChat2)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(mr)
|
|
s.Require().Len(mr.Communities(), 1)
|
|
s.Require().Len(mr.Chats(), 1)
|
|
|
|
var chatIds []string
|
|
for k := range newCommunity.Chats() {
|
|
chatIds = append(chatIds, k)
|
|
}
|
|
category := &requests.CreateCommunityCategory{
|
|
CommunityID: newCommunity.ID(),
|
|
CategoryName: "category-name",
|
|
ChatIDs: chatIds,
|
|
}
|
|
|
|
mr, err = s.alice.CreateCommunityCategory(category)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(mr)
|
|
s.Require().Len(mr.Communities(), 1)
|
|
s.Require().Len(mr.Communities()[0].Categories(), 1)
|
|
|
|
var categoryID string
|
|
for k := range mr.Communities()[0].Categories() {
|
|
categoryID = k
|
|
}
|
|
|
|
err = s.alice.SetMutePropertyOnChatsByCategory(&requests.MuteCategory{
|
|
CommunityID: newCommunity.IDString(),
|
|
CategoryID: categoryID,
|
|
MutedType: MuteTillUnmuted,
|
|
}, true)
|
|
s.Require().NoError(err)
|
|
|
|
for _, chat := range s.alice.Chats() {
|
|
if chat.CategoryID == categoryID {
|
|
s.Require().True(chat.Muted)
|
|
}
|
|
}
|
|
|
|
err = s.alice.SetMutePropertyOnChatsByCategory(&requests.MuteCategory{
|
|
CommunityID: newCommunity.IDString(),
|
|
CategoryID: categoryID,
|
|
MutedType: Unmuted,
|
|
}, false)
|
|
s.Require().NoError(err)
|
|
|
|
for _, chat := range s.alice.Chats() {
|
|
s.Require().False(chat.Muted)
|
|
}
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCheckCommunitiesToUnmute() {
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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)
|
|
|
|
currTime, err := time.Parse(time.RFC3339, time.Now().Add(-time.Hour).Format(time.RFC3339))
|
|
s.Require().NoError(err)
|
|
|
|
err = s.alice.communitiesManager.SetMuted(newCommunity.ID(), true)
|
|
s.Require().NoError(err, "SetMuted to community")
|
|
|
|
err = s.alice.communitiesManager.MuteCommunityTill(newCommunity.ID(), currTime)
|
|
s.Require().NoError(err, "SetMuteTill to community")
|
|
|
|
response, err := s.alice.CheckCommunitiesToUnmute()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1, "CheckCommunitiesToUnmute should unmute the community")
|
|
|
|
community, err := s.alice.communitiesManager.GetByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(community.Muted())
|
|
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityNotInDB() {
|
|
community, err := s.alice.communitiesManager.GetByID([]byte("0x123"))
|
|
s.Require().ErrorIs(err, communities.ErrOrgNotFound)
|
|
s.Require().Nil(community)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestMuteAllCommunityChats() {
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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)
|
|
|
|
orgChat1 := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat",
|
|
},
|
|
}
|
|
|
|
orgChat2 := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "status-core2",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat2",
|
|
},
|
|
}
|
|
|
|
mr, err = s.alice.CreateCommunityChat(newCommunity.ID(), orgChat1)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(mr)
|
|
s.Require().Len(mr.Communities(), 1)
|
|
s.Require().Len(mr.Chats(), 1)
|
|
|
|
mr, err = s.alice.CreateCommunityChat(newCommunity.ID(), orgChat2)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(mr)
|
|
s.Require().Len(mr.Communities(), 1)
|
|
s.Require().Len(mr.Chats(), 1)
|
|
|
|
muteDuration, err := s.alice.MuteDuration(MuteFor15Min)
|
|
s.Require().NoError(err)
|
|
|
|
time, err := s.alice.MuteAllCommunityChats(&requests.MuteCommunity{
|
|
CommunityID: newCommunity.ID(),
|
|
MutedType: MuteFor15Min,
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(time)
|
|
|
|
aliceCommunity, err := s.alice.GetCommunityByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(aliceCommunity.Muted())
|
|
|
|
for _, chat := range s.alice.Chats() {
|
|
if chat.CommunityID == newCommunity.IDString() {
|
|
s.Require().True(chat.Muted)
|
|
s.Require().Equal(chat.MuteTill, muteDuration)
|
|
}
|
|
}
|
|
|
|
for _, chat := range s.alice.Chats() {
|
|
if chat.CommunityID == newCommunity.IDString() {
|
|
err = s.alice.UnmuteChat(chat.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().False(chat.Muted)
|
|
break
|
|
}
|
|
}
|
|
|
|
aliceCommunity, err = s.alice.GetCommunityByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(aliceCommunity.Muted())
|
|
|
|
time, err = s.alice.UnMuteAllCommunityChats(newCommunity.IDString())
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(time)
|
|
s.Require().False(newCommunity.Muted())
|
|
|
|
for _, chat := range s.alice.Chats() {
|
|
s.Require().False(chat.Muted)
|
|
}
|
|
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestExtractDiscordChannelsAndCategories() {
|
|
|
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "discord-channel-")
|
|
s.Require().NoError(err)
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
discordMessage := &protobuf.DiscordMessage{
|
|
Id: "1234",
|
|
Type: "Default",
|
|
Timestamp: "2022-07-26T14:20:17.305+00:00",
|
|
TimestampEdited: "",
|
|
Content: "Some discord message",
|
|
Author: &protobuf.DiscordMessageAuthor{
|
|
Id: "123",
|
|
Name: "TestAuthor",
|
|
Discriminator: "456",
|
|
Nickname: "",
|
|
AvatarUrl: "",
|
|
},
|
|
}
|
|
|
|
messages := make([]*protobuf.DiscordMessage, 0)
|
|
messages = append(messages, discordMessage)
|
|
|
|
exportedDiscordData := &discord.ExportedData{
|
|
Channel: discord.Channel{
|
|
ID: "12345",
|
|
CategoryName: "test-category",
|
|
CategoryID: "6789",
|
|
Name: "test-channel",
|
|
Description: "This is a channel topic",
|
|
FilePath: tmpFile.Name(),
|
|
},
|
|
Messages: messages,
|
|
}
|
|
|
|
data, err := json.Marshal(exportedDiscordData)
|
|
s.Require().NoError(err)
|
|
|
|
err = os.WriteFile(tmpFile.Name(), data, 0666) // nolint: gosec
|
|
s.Require().NoError(err)
|
|
|
|
files := make([]string, 0)
|
|
files = append(files, tmpFile.Name())
|
|
mr, errs := s.bob.ExtractDiscordChannelsAndCategories(files)
|
|
s.Require().Len(errs, 0)
|
|
|
|
s.Require().Len(mr.DiscordCategories, 1)
|
|
s.Require().Len(mr.DiscordChannels, 1)
|
|
s.Require().Equal(mr.DiscordOldestMessageTimestamp, int(1658845217))
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestExtractDiscordChannelsAndCategories_WithErrors() {
|
|
|
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "discord-channel-2")
|
|
s.Require().NoError(err)
|
|
defer os.Remove(tmpFile.Name())
|
|
|
|
exportedDiscordData := &discord.ExportedData{
|
|
Channel: discord.Channel{
|
|
ID: "12345",
|
|
CategoryName: "test-category",
|
|
CategoryID: "6789",
|
|
Name: "test-channel",
|
|
Description: "This is a channel topic",
|
|
FilePath: tmpFile.Name(),
|
|
},
|
|
Messages: make([]*protobuf.DiscordMessage, 0),
|
|
}
|
|
|
|
data, err := json.Marshal(exportedDiscordData)
|
|
s.Require().NoError(err)
|
|
|
|
err = os.WriteFile(tmpFile.Name(), data, 0666) // nolint: gosec
|
|
s.Require().NoError(err)
|
|
|
|
files := make([]string, 0)
|
|
files = append(files, tmpFile.Name())
|
|
_, errs := s.bob.ExtractDiscordChannelsAndCategories(files)
|
|
// Expecting 1 errors since there are no messages to be extracted
|
|
s.Require().Len(errs, 1)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityBanUserRequestToJoin() {
|
|
community, _ := s.createCommunity()
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
response, err := s.owner.BanUserFromCommunity(
|
|
context.Background(),
|
|
&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))
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool { return len(r.communities) > 0 },
|
|
"no communities",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
// We try to join the org
|
|
rtj := s.alice.communitiesManager.CreateRequestToJoin(request, s.alice.account.GetCustomizationColor())
|
|
|
|
s.Require().NoError(err)
|
|
|
|
displayName, err := s.alice.settings.DisplayName()
|
|
s.Require().NoError(err)
|
|
|
|
requestToJoinProto := &protobuf.CommunityRequestToJoin{
|
|
Clock: rtj.Clock,
|
|
EnsName: rtj.ENSName,
|
|
DisplayName: displayName,
|
|
CommunityId: community.ID(),
|
|
RevealedAccounts: make([]*protobuf.RevealedAccount, 0),
|
|
}
|
|
|
|
s.Require().NoError(err)
|
|
|
|
messageState := s.owner.buildMessageState()
|
|
messageState.CurrentMessageState = &CurrentMessageState{}
|
|
|
|
messageState.CurrentMessageState.PublicKey = &s.alice.identity.PublicKey
|
|
|
|
statusMessage := v1protocol.StatusMessage{}
|
|
statusMessage.TransportLayer.Dst = community.PublicKey()
|
|
err = s.owner.HandleCommunityRequestToJoin(messageState, requestToJoinProto, &statusMessage)
|
|
|
|
s.Require().ErrorContains(err, "can't request access")
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityMaxNumberOfMembers() {
|
|
john := s.newMessenger()
|
|
_, err := john.Start()
|
|
s.Require().NoError(err)
|
|
|
|
defer TearDownMessenger(&s.Suite, john)
|
|
|
|
// Bring back the original values
|
|
defer communities.SetMaxNbMembers(5000)
|
|
defer communities.SetMaxNbPendingRequestedMembers(100)
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
communities.SetMaxNbMembers(2)
|
|
communities.SetMaxNbPendingRequestedMembers(1)
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.advertiseCommunityTo(community, s.owner, s.bob)
|
|
s.advertiseCommunityTo(community, s.owner, john)
|
|
|
|
// Alice joins the community correctly
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// Bob also tries to join, but he will be put in the requests to join to approve and won't join
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
response, err := s.bob.RequestToJoinCommunity(request)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
requestID := response.RequestsToJoinCommunity()[0].ID
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.owner,
|
|
func(r *MessengerResponse) bool {
|
|
for _, req := range r.RequestsToJoinCommunity() {
|
|
if reflect.DeepEqual(req.ID, requestID) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"no request to join",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
s.Require().Equal(communities.RequestToJoinStatePending, response.RequestsToJoinCommunity()[0].State)
|
|
|
|
// We confirm that there are still 2 members only and the access setting is now manual
|
|
updatedCommunity, err := s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(updatedCommunity.Members(), 2)
|
|
s.Require().Equal(protobuf.CommunityPermissions_MANUAL_ACCEPT, updatedCommunity.Permissions().Access)
|
|
|
|
// John also tries to join, but he his request will be ignored as it exceeds the max number of pending requests
|
|
requestJohn := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
response, err = john.RequestToJoinCommunity(requestJohn)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
requestJohnID := response.RequestsToJoinCommunity()[0].ID
|
|
|
|
_, err = WaitOnMessengerResponse(
|
|
s.owner,
|
|
func(r *MessengerResponse) bool {
|
|
for _, req := range r.RequestsToJoinCommunity() {
|
|
if reflect.DeepEqual(req.ID, requestJohnID) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"no request to join",
|
|
)
|
|
s.Require().Error(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestHandleImport() {
|
|
community, chat := s.createCommunity()
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// Check that there are no messages in the chat at first
|
|
chat, err := s.alice.persistence.Chat(chat.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(chat)
|
|
s.Require().Equal(0, int(chat.UnviewedMessagesCount))
|
|
|
|
// Create an message that will be imported
|
|
testMessage := protobuf.ChatMessage{
|
|
Text: "abc123",
|
|
ChatId: chat.ID,
|
|
ContentType: protobuf.ChatMessage_TEXT_PLAIN,
|
|
MessageType: protobuf.MessageType_COMMUNITY_CHAT,
|
|
Clock: 1,
|
|
Timestamp: 1,
|
|
}
|
|
encodedPayload, err := proto.Marshal(&testMessage)
|
|
s.Require().NoError(err)
|
|
wrappedPayload, err := v1protocol.WrapMessageV1(
|
|
encodedPayload,
|
|
protobuf.ApplicationMetadataMessage_CHAT_MESSAGE,
|
|
s.owner.identity,
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
message := &types.Message{}
|
|
message.Sig = crypto.FromECDSAPub(&s.owner.identity.PublicKey)
|
|
message.Payload = wrappedPayload
|
|
|
|
filter := s.alice.transport.FilterByChatID(chat.ID)
|
|
importedMessages := make(map[transport.Filter][]*types.Message, 0)
|
|
|
|
importedMessages[*filter] = append(importedMessages[*filter], message)
|
|
|
|
// Import that message
|
|
err = s.alice.handleImportedMessages(importedMessages)
|
|
s.Require().NoError(err)
|
|
|
|
// Get the chat again and see that there is still no unread message because we don't count import messages
|
|
chat, err = s.alice.persistence.Chat(chat.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(chat)
|
|
s.Require().Equal(0, int(chat.UnviewedMessagesCount))
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestGetCommunityIdFromKey() {
|
|
publicKey := "0x029e4777ce55f20373db33546c8681a082bd181d665c87e18d4306766de9302b53"
|
|
privateKey := "0x3f932031cb5f94ba7eb8ab4c824c3677973ab01fde65d1b89e0b3f470003a2cd"
|
|
|
|
// Public key returns the same
|
|
communityID := GetCommunityIDFromKey(publicKey)
|
|
s.Require().Equal(communityID, publicKey)
|
|
|
|
// Private key returns the public key
|
|
communityID = GetCommunityIDFromKey(privateKey)
|
|
s.Require().Equal(communityID, publicKey)
|
|
}
|
|
|
|
type testPermissionChecker struct {
|
|
}
|
|
|
|
func (t *testPermissionChecker) CheckPermissionToJoin(*communities.Community, []gethcommon.Address) (*communities.CheckPermissionToJoinResponse, error) {
|
|
return &communities.CheckPermissionsResponse{Satisfied: true}, nil
|
|
|
|
}
|
|
|
|
func (t *testPermissionChecker) CheckPermissions(permissionsParsedData *communities.PreParsedCommunityPermissionsData, accountsAndChainIDs []*communities.AccountChainIDsCombination, shortcircuit bool) (*communities.CheckPermissionsResponse, error) {
|
|
return &communities.CheckPermissionsResponse{Satisfied: true}, nil
|
|
}
|
|
|
|
func (t *testPermissionChecker) CheckPermissionsWithPreFetchedData(permissionsParsedData *communities.PreParsedCommunityPermissionsData, accountsAndChainIDs []*communities.AccountChainIDsCombination, shortcircuit bool, collectiblesOwners communities.CollectiblesOwners) (*communities.CheckPermissionsResponse, error) {
|
|
return &communities.CheckPermissionsResponse{Satisfied: true}, nil
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestStartCommunityRekeyLoop() {
|
|
community, chat := createEncryptedCommunity(&s.Suite, s.owner)
|
|
s.Require().True(community.Encrypted())
|
|
s.Require().True(community.ChannelEncrypted(chat.CommunityChatID()))
|
|
|
|
s.owner.communitiesManager.PermissionChecker = &testPermissionChecker{}
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.bob)
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.bob)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// Check keys in the database
|
|
communityKeys, err := s.owner.sender.GetKeysForGroup(community.ID())
|
|
s.Require().NoError(err)
|
|
communityKeyCount := len(communityKeys)
|
|
|
|
channelKeys, err := s.owner.sender.GetKeysForGroup([]byte(chat.ID))
|
|
s.Require().NoError(err)
|
|
channelKeyCount := len(channelKeys)
|
|
|
|
// Check that rekeying is occurring by counting the number of keyIDs in the encryptor's DB
|
|
// This test could be flaky, as the rekey function may not be finished before RekeyInterval * 2 has passed
|
|
for i := 0; i < 5; i++ {
|
|
time.Sleep(s.owner.communitiesManager.RekeyInterval * 2)
|
|
communityKeys, err = s.owner.sender.GetKeysForGroup(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Greater(len(communityKeys), communityKeyCount)
|
|
communityKeyCount = len(communityKeys)
|
|
|
|
channelKeys, err = s.owner.sender.GetKeysForGroup([]byte(chat.ID))
|
|
s.Require().NoError(err)
|
|
s.Require().Greater(len(channelKeys), channelKeyCount)
|
|
channelKeyCount = len(channelKeys)
|
|
}
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityRekeyAfterBan() {
|
|
s.owner.communitiesManager.RekeyInterval = 500 * time.Minute
|
|
|
|
// Create a new community
|
|
response, err := s.owner.CreateCommunity(
|
|
&requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#57a7e5",
|
|
Description: "status community description",
|
|
},
|
|
true,
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Communities()[0].Members(), 1)
|
|
|
|
// Check community is present in the DB and has default values we care about
|
|
c, err := s.owner.GetCommunityByID(response.Communities()[0].ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(c.Encrypted())
|
|
// TODO some check that there are no keys for the community. Alt for s.Require().Zero(c.RekeyedAt().Unix())
|
|
|
|
_, err = s.owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
|
|
CommunityID: c.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{{
|
|
ContractAddresses: map[uint64]string{3: "0x933"},
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
Symbol: "STT",
|
|
Name: "Status Test Token",
|
|
AmountInWei: "10000000000000000000",
|
|
Decimals: 18,
|
|
}},
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
c, err = s.owner.GetCommunityByID(c.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(c.Encrypted())
|
|
|
|
s.advertiseCommunityTo(c, s.owner, s.bob)
|
|
s.advertiseCommunityTo(c, s.owner, s.alice)
|
|
|
|
s.owner.communitiesManager.PermissionChecker = &testPermissionChecker{}
|
|
|
|
s.joinCommunity(c, s.owner, s.bob)
|
|
s.joinCommunity(c, s.owner, s.alice)
|
|
|
|
// Check the Alice and Bob are members of the community
|
|
c, err = s.owner.GetCommunityByID(c.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(c.HasMember(&s.alice.identity.PublicKey))
|
|
s.Require().True(c.HasMember(&s.bob.identity.PublicKey))
|
|
|
|
// Make sure at least one key makes it to alice
|
|
response, err = WaitOnMessengerResponse(s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
keys, err := s.alice.encryptor.GetKeysForGroup(response.Communities()[0].ID())
|
|
if err != nil || len(keys) != 1 {
|
|
return false
|
|
}
|
|
return true
|
|
|
|
},
|
|
"alice does not have enough keys",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
response, err = s.owner.BanUserFromCommunity(context.Background(), &requests.BanUserFromCommunity{
|
|
CommunityID: c.ID(),
|
|
User: common.PubkeyToHexBytes(&s.bob.identity.PublicKey),
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
s.Require().False(response.Communities()[0].HasMember(&s.bob.identity.PublicKey))
|
|
|
|
// Check bob has been banned
|
|
response, err = WaitOnMessengerResponse(s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) == 1 && !r.Communities()[0].HasMember(&s.bob.identity.PublicKey)
|
|
|
|
},
|
|
"alice didn't receive updated description",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
response, err = WaitOnMessengerResponse(s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
keys, err := s.alice.encryptor.GetKeysForGroup(response.Communities()[0].ID())
|
|
if err != nil || len(keys) < 2 {
|
|
return false
|
|
}
|
|
return true
|
|
|
|
},
|
|
"alice hasn't received updated key",
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityRekeyAfterBanDisableCompatibility() {
|
|
common.RekeyCompatibility = false
|
|
s.owner.communitiesManager.RekeyInterval = 500 * time.Minute
|
|
|
|
// Create a new community
|
|
response, err := s.owner.CreateCommunity(
|
|
&requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#57a7e5",
|
|
Description: "status community description",
|
|
},
|
|
true,
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
// Check community is present in the DB and has default values we care about
|
|
c, err := s.owner.GetCommunityByID(response.Communities()[0].ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(c.Encrypted())
|
|
// TODO some check that there are no keys for the community. Alt for s.Require().Zero(c.RekeyedAt().Unix())
|
|
|
|
_, err = s.owner.CreateCommunityTokenPermission(&requests.CreateCommunityTokenPermission{
|
|
CommunityID: c.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{{
|
|
ContractAddresses: map[uint64]string{3: "0x933"},
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
Symbol: "STT",
|
|
Name: "Status Test Token",
|
|
AmountInWei: "10000000000000000000",
|
|
Decimals: 18,
|
|
}},
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
c, err = s.owner.GetCommunityByID(c.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(c.Encrypted())
|
|
|
|
s.advertiseCommunityTo(c, s.owner, s.bob)
|
|
s.advertiseCommunityTo(c, s.owner, s.alice)
|
|
|
|
s.owner.communitiesManager.PermissionChecker = &testPermissionChecker{}
|
|
|
|
s.joinCommunity(c, s.owner, s.bob)
|
|
s.joinCommunity(c, s.owner, s.alice)
|
|
|
|
// Check the Alice and Bob are members of the community
|
|
c, err = s.owner.GetCommunityByID(c.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(c.HasMember(&s.alice.identity.PublicKey))
|
|
s.Require().True(c.HasMember(&s.bob.identity.PublicKey))
|
|
|
|
// Make sure at least one key makes it to alice
|
|
response, err = WaitOnMessengerResponse(s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
keys, err := s.alice.encryptor.GetKeysForGroup(response.Communities()[0].ID())
|
|
if err != nil || len(keys) != 1 {
|
|
return false
|
|
}
|
|
return true
|
|
|
|
},
|
|
"alice does not have enough keys",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
response, err = s.owner.BanUserFromCommunity(context.Background(), &requests.BanUserFromCommunity{
|
|
CommunityID: c.ID(),
|
|
User: common.PubkeyToHexBytes(&s.bob.identity.PublicKey),
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
s.Require().False(response.Communities()[0].HasMember(&s.bob.identity.PublicKey))
|
|
|
|
// Check bob has been banned
|
|
response, err = WaitOnMessengerResponse(s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) == 1 && !r.Communities()[0].HasMember(&s.bob.identity.PublicKey)
|
|
|
|
},
|
|
"alice didn't receive updated description",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
response, err = WaitOnMessengerResponse(s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
keys, err := s.alice.encryptor.GetKeysForGroup(response.Communities()[0].ID())
|
|
if err != nil || len(keys) < 2 {
|
|
return false
|
|
}
|
|
return true
|
|
|
|
},
|
|
"alice hasn't received updated key",
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRetrieveBigCommunity() {
|
|
bigEmoji := make([]byte, 4*1024*1024) // 4 MB
|
|
description := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: "status community description",
|
|
Emoji: string(bigEmoji),
|
|
}
|
|
|
|
// checks that private messages are segmented
|
|
// (community is advertised through `SendPrivate`)
|
|
response, err := s.owner.CreateCommunity(description, true)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
community := response.Communities()[0]
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// checks that public messages are segmented
|
|
// (community is advertised through `SendPublic`)
|
|
updatedDescription := "status updated community description"
|
|
_, err = s.owner.EditCommunity(&requests.EditCommunity{
|
|
CommunityID: community.ID(),
|
|
CreateCommunity: requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
Name: "status",
|
|
Color: "#ffffff",
|
|
Description: updatedDescription,
|
|
Emoji: string(bigEmoji),
|
|
},
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// alice receives updated description
|
|
_, err = WaitOnMessengerResponse(s.alice, func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) > 0 && r.Communities()[0].DescriptionText() == updatedDescription
|
|
}, "updated description not received")
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestRequestAndCancelCommunityAdminOffline() {
|
|
ctx := context.Background()
|
|
|
|
community, _ := s.createCommunity()
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
|
|
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)
|
|
|
|
messageState := s.alice.buildMessageState()
|
|
messageState.CurrentMessageState = &CurrentMessageState{}
|
|
|
|
messageState.CurrentMessageState.PublicKey = &s.alice.identity.PublicKey
|
|
|
|
statusMessage := v1protocol.StatusMessage{}
|
|
statusMessage.TransportLayer.Dst = community.PublicKey()
|
|
|
|
requestToJoinProto := &protobuf.CommunityRequestToJoin{
|
|
Clock: requestToJoin1.Clock,
|
|
EnsName: requestToJoin1.ENSName,
|
|
DisplayName: "Alice",
|
|
CommunityId: community.ID(),
|
|
}
|
|
|
|
err = s.owner.HandleCommunityRequestToJoin(messageState, requestToJoinProto, &statusMessage)
|
|
s.Require().NoError(err)
|
|
ownerCommunity, err := s.owner.GetCommunityByID(community.ID())
|
|
// Check Alice has successfully joined at owner side, Because message order was correct
|
|
s.Require().True(ownerCommunity.HasMember(s.alice.IdentityPublicKey()))
|
|
s.Require().NoError(err)
|
|
|
|
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, 1)
|
|
s.Require().Equal(allCommunities[0].ID(), community.ID())
|
|
s.Require().Equal(allCommunities[0].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)
|
|
|
|
requestToJoin2 := response.RequestsToJoinCommunity()[0]
|
|
|
|
s.Require().NotNil(requestToJoin2)
|
|
s.Require().Equal(community.ID(), requestToJoin2.CommunityID)
|
|
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)
|
|
|
|
requestToCancel := &requests.CancelRequestToJoinCommunity{ID: requestToJoin1.ID}
|
|
response, err = s.alice.CancelRequestToJoinCommunity(ctx, requestToCancel)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
s.Require().Equal(communities.RequestToJoinStateCanceled, response.RequestsToJoinCommunity()[0].State)
|
|
|
|
messageState = s.alice.buildMessageState()
|
|
messageState.CurrentMessageState = &CurrentMessageState{}
|
|
|
|
messageState.CurrentMessageState.PublicKey = &s.alice.identity.PublicKey
|
|
|
|
statusMessage.TransportLayer.Dst = community.PublicKey()
|
|
|
|
requestToJoinCancelProto := &protobuf.CommunityRequestToJoinResponse{
|
|
CommunityId: community.ID(),
|
|
Clock: requestToJoin1.Clock + 1,
|
|
Accepted: true,
|
|
}
|
|
|
|
err = s.alice.HandleCommunityRequestToJoinResponse(messageState, requestToJoinCancelProto, &statusMessage)
|
|
s.Require().NoError(err)
|
|
aliceJoinedCommunities, err := s.alice.JoinedCommunities()
|
|
s.Require().NoError(err)
|
|
// Make sure on Alice side she hasn't joined any communities
|
|
s.Require().Empty(aliceJoinedCommunities)
|
|
|
|
// pull to make sure it has been saved
|
|
myRequestToJoinId := communities.CalculateRequestID(s.alice.IdentityPublicKeyString(), community.ID())
|
|
canceledRequestToJoin, err := s.alice.communitiesManager.GetRequestToJoin(myRequestToJoinId)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(canceledRequestToJoin)
|
|
s.Require().Equal(canceledRequestToJoin.State, communities.RequestToJoinStateCanceled)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
messageState = s.alice.buildMessageState()
|
|
messageState.CurrentMessageState = &CurrentMessageState{}
|
|
|
|
messageState.CurrentMessageState.PublicKey = &s.alice.identity.PublicKey
|
|
|
|
statusMessage.TransportLayer.Dst = community.PublicKey()
|
|
|
|
requestToJoinResponseProto := &protobuf.CommunityRequestToJoinResponse{
|
|
Clock: canceledRequestToJoin.Clock,
|
|
CommunityId: community.ID(),
|
|
Accepted: true,
|
|
}
|
|
|
|
err = s.alice.HandleCommunityRequestToJoinResponse(messageState, requestToJoinResponseProto, &statusMessage)
|
|
s.Require().NoError(err)
|
|
// Make sure alice is NOT a member of the community that she cancelled her request to join to
|
|
s.Require().False(community.HasMember(s.alice.IdentityPublicKey()))
|
|
// Make sure there are no AC notifications for Alice
|
|
aliceNotifications, err := s.alice.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(aliceNotifications.Notifications, 0)
|
|
|
|
// Retrieve activity center notifications for admin to make sure the request notification is deleted
|
|
notifications, err := s.owner.ActivityCenterNotifications(ActivityCenterNotificationsRequest{
|
|
Cursor: "",
|
|
Limit: 10,
|
|
ActivityTypes: []ActivityCenterType{},
|
|
ReadType: ActivityCenterQueryParamsReadUnread,
|
|
})
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().Len(notifications.Notifications, 0)
|
|
cancelRequestToJoin2 := response.RequestsToJoinCommunity()[0]
|
|
s.Require().NotNil(cancelRequestToJoin2)
|
|
s.Require().Equal(community.ID(), cancelRequestToJoin2.CommunityID)
|
|
s.Require().False(cancelRequestToJoin2.Our)
|
|
s.Require().NotEmpty(cancelRequestToJoin2.ID)
|
|
s.Require().NotEmpty(cancelRequestToJoin2.Clock)
|
|
s.Require().Equal(cancelRequestToJoin2.PublicKey, common.PubkeyToHex(&s.alice.identity.PublicKey))
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestCommunityLastOpenedAt() {
|
|
community, _ := s.createCommunity()
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
// Mock frontend triggering communityUpdateLastOpenedAt
|
|
lastOpenedAt1, err := s.alice.CommunityUpdateLastOpenedAt(community.IDString())
|
|
s.Require().NoError(err)
|
|
|
|
// Check lastOpenedAt was updated
|
|
s.Require().True(lastOpenedAt1 > 0)
|
|
|
|
// Nap for a bit
|
|
time.Sleep(time.Second)
|
|
|
|
// Check lastOpenedAt was successfully updated twice
|
|
lastOpenedAt2, err := s.alice.CommunityUpdateLastOpenedAt(community.IDString())
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().True(lastOpenedAt2 > lastOpenedAt1)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSyncCommunityLastOpenedAt() {
|
|
// Create new device
|
|
alicesOtherDevice := s.createOtherDevice(s.alice)
|
|
PairDevices(&s.Suite, alicesOtherDevice, s.alice)
|
|
|
|
// Create a community
|
|
createCommunityReq := &requests.CreateCommunity{
|
|
Membership: protobuf.CommunityPermissions_MANUAL_ACCEPT,
|
|
Name: "new community",
|
|
Color: "#000000",
|
|
Description: "new community description",
|
|
}
|
|
|
|
mr, err := s.alice.CreateCommunity(createCommunityReq, true)
|
|
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)
|
|
|
|
// Mock frontend triggering communityUpdateLastOpenedAt
|
|
lastOpenedAt, err := s.alice.CommunityUpdateLastOpenedAt(newCommunity.IDString())
|
|
s.Require().NoError(err)
|
|
|
|
// Check lastOpenedAt was updated
|
|
s.Require().True(lastOpenedAt > 0)
|
|
|
|
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
|
|
})
|
|
otherDeviceCommunity, err := alicesOtherDevice.communitiesManager.GetByID(newCommunity.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(otherDeviceCommunity.LastOpenedAt() > 0)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestBanUserAndDeleteAllUserMessages() {
|
|
community, _ := s.createCommunity()
|
|
|
|
orgChat := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "chat test delete messages",
|
|
Emoji: "😎",
|
|
Description: "status-core community chat",
|
|
},
|
|
}
|
|
response, err := s.owner.CreateCommunityChat(community.ID(), orgChat)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
|
|
community = response.Communities()[0]
|
|
communityChat := response.Chats()[0]
|
|
|
|
s.advertiseCommunityTo(community, s.owner, s.alice)
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
inputMessage := buildTestMessage(*communityChat)
|
|
|
|
sendResponse, err := s.alice.SendChatMessage(context.Background(), inputMessage)
|
|
s.NoError(err)
|
|
s.Require().NotNil(sendResponse)
|
|
s.Require().Len(sendResponse.Messages(), 1)
|
|
messageID := sendResponse.Messages()[0].ID
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.owner,
|
|
func(r *MessengerResponse) bool {
|
|
if len(r.Messages()) == 0 {
|
|
return false
|
|
}
|
|
|
|
for _, message := range r.Messages() {
|
|
if message.ID == messageID {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(messageID, response.Messages()[0].ID)
|
|
|
|
response, err = s.owner.BanUserFromCommunity(
|
|
context.Background(),
|
|
&requests.BanUserFromCommunity{
|
|
CommunityID: community.ID(),
|
|
User: common.PubkeyToHexBytes(&s.alice.identity.PublicKey),
|
|
DeleteAllMessages: true,
|
|
},
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Messages(), 0)
|
|
s.Require().Len(response.RemovedMessages(), 0)
|
|
s.Require().Len(response.DeletedMessages(), 1)
|
|
// we are removing last message, so we must get chat update too
|
|
|
|
community = response.Communities()[0]
|
|
s.Require().False(community.HasMember(&s.alice.identity.PublicKey))
|
|
s.Require().True(community.IsBanned(&s.alice.identity.PublicKey))
|
|
s.Require().Len(community.PendingAndBannedMembers(), 1)
|
|
s.Require().Equal(community.PendingAndBannedMembers()[s.alice.IdentityPublicKeyString()], communities.CommunityMemberBanWithAllMessagesDelete)
|
|
|
|
response, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return r != nil && len(r.DeletedMessages()) > 0
|
|
},
|
|
"no removed message for alice",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Messages(), 0)
|
|
s.Require().Len(response.ActivityCenterNotifications(), 1)
|
|
s.Require().Len(response.RemovedMessages(), 0)
|
|
s.Require().Len(response.DeletedMessages(), 1)
|
|
// we are removing last message, so we must get chat update too
|
|
|
|
community = response.Communities()[0]
|
|
s.Require().False(community.HasMember(&s.alice.identity.PublicKey))
|
|
s.Require().True(community.IsBanned(&s.alice.identity.PublicKey))
|
|
s.Require().Len(community.PendingAndBannedMembers(), 1)
|
|
s.Require().Equal(community.PendingAndBannedMembers()[s.alice.IdentityPublicKeyString()], communities.CommunityMemberBanWithAllMessagesDelete)
|
|
s.Require().False(community.Joined())
|
|
s.Require().False(community.Spectated())
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestIsDisplayNameDupeOfCommunityMember() {
|
|
community, _ := s.createCommunity()
|
|
advertiseCommunityToUserOldWay(&s.Suite, community, s.owner, s.alice)
|
|
|
|
s.joinCommunity(community, s.owner, s.alice)
|
|
|
|
result, err := s.alice.IsDisplayNameDupeOfCommunityMember("Charlie")
|
|
s.Require().NoError(err)
|
|
s.Require().True(result)
|
|
|
|
result, err = s.alice.IsDisplayNameDupeOfCommunityMember("Alice")
|
|
s.Require().NoError(err)
|
|
s.Require().True(result)
|
|
|
|
result, err = s.alice.IsDisplayNameDupeOfCommunityMember("Bobby")
|
|
s.Require().NoError(err)
|
|
s.Require().False(result)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) sendImageToCommunity(sender *Messenger, chatID string) *common.Message {
|
|
ctx := context.Background()
|
|
messageToSend := common.NewMessage()
|
|
messageToSend.ChatId = chatID
|
|
messageToSend.ContentType = protobuf.ChatMessage_IMAGE
|
|
|
|
// base64 image
|
|
encodedB64Image := "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII"
|
|
decodedBytes, _ := base64.StdEncoding.DecodeString(encodedB64Image)
|
|
|
|
messageToSend.Payload = &protobuf.ChatMessage_Image{
|
|
Image: &protobuf.ImageMessage{
|
|
Format: 1, // PNG
|
|
Payload: decodedBytes,
|
|
},
|
|
}
|
|
|
|
response, err := sender.SendChatMessage(ctx, messageToSend)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
sentMessage := response.Messages()[0]
|
|
|
|
receivers := []*Messenger{s.alice, s.bob, s.owner}
|
|
for _, receiver := range receivers {
|
|
if receiver == sender {
|
|
continue
|
|
}
|
|
_, err = WaitOnMessengerResponse(receiver, func(response *MessengerResponse) bool {
|
|
return len(response.Messages()) == 1 && response.Messages()[0].ID == sentMessage.ID
|
|
}, "receiver did not receive message")
|
|
s.Require().NoError(err)
|
|
}
|
|
return sentMessage
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestSerializedCommunities() {
|
|
addMediaServer := func(messenger *Messenger) {
|
|
mediaServer, err := server.NewMediaServer(messenger.database, nil, nil, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NoError(mediaServer.Start())
|
|
messenger.SetMediaServer(mediaServer)
|
|
}
|
|
addMediaServer(s.owner)
|
|
|
|
community, _ := s.createCommunity()
|
|
// update community description
|
|
description := community.Description()
|
|
identImageName := "small"
|
|
identImagePayload := []byte("123")
|
|
description.Identity = &protobuf.ChatIdentity{
|
|
Images: map[string]*protobuf.IdentityImage{
|
|
identImageName: {
|
|
Payload: identImagePayload,
|
|
},
|
|
},
|
|
}
|
|
// #nosec G101
|
|
tokenImageInBase64 := "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/tdgBUgAAAAASUVORK5CYII="
|
|
description.CommunityTokensMetadata = []*protobuf.CommunityTokenMetadata{
|
|
{
|
|
Image: tokenImageInBase64,
|
|
Symbol: "STT",
|
|
},
|
|
}
|
|
description.Clock = description.Clock + 1
|
|
community.Edit(description)
|
|
s.Require().NoError(s.owner.communitiesManager.SaveCommunity(community))
|
|
|
|
// check edit was successful
|
|
b, err := s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(b)
|
|
s.Len(b.Description().CommunityTokensMetadata, 1)
|
|
s.Equal(tokenImageInBase64, b.Description().CommunityTokensMetadata[0].Image)
|
|
s.Len(b.Description().Identity.Images, 1)
|
|
s.Equal(identImagePayload, b.Description().Identity.Images[identImageName].Payload)
|
|
|
|
c, err := s.owner.Communities()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(c, 1)
|
|
d, err := json.Marshal(c)
|
|
s.Require().NoError(err)
|
|
|
|
type Image struct {
|
|
Uri string `json:"uri"`
|
|
}
|
|
var communityData []struct {
|
|
Images map[string]Image `json:"images"`
|
|
CommunityTokensMetadata []struct {
|
|
Image string `json:"image"`
|
|
Symbol string `json:"symbol"`
|
|
} `json:"communityTokensMetadata"`
|
|
}
|
|
err = json.Unmarshal(d, &communityData)
|
|
s.Require().NoError(err)
|
|
// Check community description image
|
|
s.Require().NotEmpty(communityData[0].Images[identImageName])
|
|
image := communityData[0].Images[identImageName]
|
|
s.T().Log(fmt.Sprintf("Image URL (%s):", identImageName), image)
|
|
e, err := s.fetchImage(image.Uri)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(identImagePayload, e)
|
|
|
|
imageUrlWithoutCommunityID, err := s.removeUrlParam(image.Uri, "communityID")
|
|
s.Require().NoError(err)
|
|
e, err = s.fetchImage(imageUrlWithoutCommunityID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(e, 0)
|
|
|
|
imageUrlWithWrongCommunityID, err := s.updateUrlParam(image.Uri, "communityID", "0x0")
|
|
s.Require().NoError(err)
|
|
e, err = s.fetchImage(imageUrlWithWrongCommunityID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(e, 0)
|
|
|
|
// Check communityTokensMetadata image
|
|
s.Require().NotEmpty(communityData[0].CommunityTokensMetadata)
|
|
tokenImageUrl := communityData[0].CommunityTokensMetadata[0].Image
|
|
s.T().Log("Community Token Metadata Image:", tokenImageUrl)
|
|
s.T().Log("Community Token Metadata Symbol:", communityData[0].CommunityTokensMetadata[0].Symbol)
|
|
f, err := s.fetchImage(tokenImageUrl)
|
|
s.Require().NoError(err)
|
|
tokenImagePayload, err := images.GetPayloadFromURI(tokenImageInBase64)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(tokenImagePayload, f)
|
|
|
|
tokenImageUrlWithoutCommunityID, err := s.removeUrlParam(tokenImageUrl, "communityID")
|
|
s.Require().NoError(err)
|
|
f, err = s.fetchImage(tokenImageUrlWithoutCommunityID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(f, 0)
|
|
|
|
tokenImageUrlWithWrongCommunityID, err := s.updateUrlParam(tokenImageUrl, "communityID", "0x0")
|
|
s.Require().NoError(err)
|
|
f, err = s.fetchImage(tokenImageUrlWithWrongCommunityID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(f, 0)
|
|
|
|
tokenImageUrlWithoutSymbol, err := s.removeUrlParam(tokenImageUrl, "symbol")
|
|
s.Require().NoError(err)
|
|
f, err = s.fetchImage(tokenImageUrlWithoutSymbol)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(f, 0)
|
|
|
|
tokenImageUrlWithWrongSymbol, err := s.updateUrlParam(tokenImageUrl, "symbol", "WRONG")
|
|
s.Require().NoError(err)
|
|
f, err = s.fetchImage(tokenImageUrlWithWrongSymbol)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(f, 0)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) updateUrlParam(rawURL string, name, val string) (string, error) {
|
|
parsedURL, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
queryParams := parsedURL.Query()
|
|
queryParams.Set(name, val)
|
|
parsedURL.RawQuery = queryParams.Encode()
|
|
return parsedURL.String(), nil
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) removeUrlParam(rawURL, name string) (string, error) {
|
|
parsedURL, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
queryParams := parsedURL.Query()
|
|
queryParams.Del(name)
|
|
parsedURL.RawQuery = queryParams.Encode()
|
|
return parsedURL.String(), nil
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) fetchImage(fullURL string) ([]byte, error) {
|
|
req, err := http.NewRequest("GET", fullURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client := &http.Client{
|
|
Transport: &http.Transport{
|
|
TLSClientConfig: &tls.Config{
|
|
MinVersion: tls.VersionTLS12,
|
|
InsecureSkipVerify: true, // nolint: gosec
|
|
},
|
|
},
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
return ioutil.ReadAll(resp.Body)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestMemberMessagesHasImageLink() {
|
|
// GIVEN
|
|
addMediaServer := func(messenger *Messenger) {
|
|
mediaServer, err := server.NewMediaServer(messenger.database, nil, nil, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().NoError(mediaServer.Start())
|
|
messenger.SetMediaServer(mediaServer)
|
|
}
|
|
addMediaServer(s.alice)
|
|
addMediaServer(s.bob)
|
|
addMediaServer(s.owner)
|
|
community, communityChat := s.createCommunity()
|
|
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.alice)
|
|
joinCommunity(&s.Suite, community, s.owner, s.alice, request, "")
|
|
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.bob)
|
|
joinCommunity(&s.Suite, community, s.owner, s.bob, request, "")
|
|
|
|
// WHEN: alice sends an image message
|
|
sentMessage := s.sendImageToCommunity(s.alice, communityChat.ID)
|
|
|
|
// THEN: everyone see alice message with image link
|
|
requireMessageWithImage := func(messenger *Messenger, memberPubKey string, communityID string) {
|
|
storedMessages, err := messenger.GetCommunityMemberAllMessages(
|
|
&requests.CommunityMemberMessages{
|
|
CommunityID: communityID,
|
|
MemberPublicKey: memberPubKey})
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(1, len(storedMessages))
|
|
memberMessage := storedMessages[0]
|
|
s.Require().Equal(sentMessage.ID, memberMessage.ID)
|
|
s.Require().True(strings.HasPrefix(memberMessage.ImageLocalURL, "https://Localhost"))
|
|
}
|
|
communityID := community.IDString()
|
|
alicePubKey := s.alice.IdentityPublicKeyString()
|
|
|
|
requireMessageWithImage(s.owner, alicePubKey, communityID)
|
|
requireMessageWithImage(s.alice, alicePubKey, communityID)
|
|
requireMessageWithImage(s.bob, alicePubKey, communityID)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestOpenAndNotJoinedCommunityNewChannelIsNotEmpty() {
|
|
// Create an open community
|
|
community, _ := s.createCommunity()
|
|
s.Require().Len(community.Chats(), 1)
|
|
s.Require().False(community.Encrypted())
|
|
|
|
// Bob joins the community
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.bob)
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
joinCommunity(&s.Suite, community, s.owner, s.bob, request, "")
|
|
|
|
// Alice just observes the community
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.alice)
|
|
_, err := s.alice.SpectateCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
aliceCommunity, err := s.alice.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(aliceCommunity.Chats(), 1)
|
|
|
|
// Owner creates a new channel
|
|
newChannel := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "new channel",
|
|
Emoji: "",
|
|
Description: "chat created after joining the community",
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityChat(community.ID(), newChannel)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.CommunityChanges, 1)
|
|
s.Require().Len(response.CommunityChanges[0].ChatsAdded, 1)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Chats(), 1)
|
|
s.Require().Len(response.Chats()[0].Members, 2)
|
|
for _, chat := range response.Communities()[0].Chats() {
|
|
s.Require().Len(chat.Members, 2)
|
|
}
|
|
|
|
// Check Alice gets the correct member list for a new channel
|
|
_, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
if len(r.Chats()) == 1 && len(r.Communities()) > 0 {
|
|
for _, chat := range r.Chats() {
|
|
s.Require().Len(chat.Members, 2)
|
|
}
|
|
for _, chat := range r.Communities()[0].Chats() {
|
|
s.Require().Len(chat.Members, 2)
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
},
|
|
"no commiunity message for Alice",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
aliceCommunity, err = s.alice.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(aliceCommunity.Chats(), 2)
|
|
for _, chat := range aliceCommunity.Chats() {
|
|
s.Require().Len(chat.Members, 2)
|
|
}
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) sendMention(sender *Messenger, chatID string) *common.Message {
|
|
ctx := context.Background()
|
|
messageToSend := common.NewMessage()
|
|
messageToSend.ChatId = chatID
|
|
messageToSend.ContentType = protobuf.ChatMessage_TEXT_PLAIN
|
|
messageToSend.Text = "Hello @" + common.EveryoneMentionTag
|
|
|
|
response, err := sender.SendChatMessage(ctx, messageToSend)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().True(response.Messages()[0].Mentioned)
|
|
return response.Messages()[0]
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestAliceDoesNotReceiveMentionWhenSpectating() {
|
|
// GIVEN: Create an open community
|
|
community, communityChat := s.createCommunity()
|
|
community, err := s.owner.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(community.Chats(), 1)
|
|
s.Require().False(community.Encrypted())
|
|
|
|
// Alice SPECTATES the community
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.alice)
|
|
_, err = s.alice.SpectateCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
aliceCommunity, err := s.alice.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Contains(aliceCommunity.ChatIDs(), communityChat.ID)
|
|
|
|
// Bob JOINS the community
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.bob)
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
joinCommunity(&s.Suite, community, s.owner, s.bob, request, "")
|
|
|
|
// Check Alice gets the updated community
|
|
_, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) > 0 && r.Communities()[0].MembersCount() == 2
|
|
},
|
|
"no community updates for Alice",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// WHEN: Bob sends a message to a channel with mention
|
|
sentMessage := s.sendMention(s.bob, communityChat.ID)
|
|
|
|
// THEN: Check Alice gets the message, but no activity center notification
|
|
_, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 0 &&
|
|
r.Messages()[0].ID == sentMessage.ID
|
|
},
|
|
"no message for Alice",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// Alice joins community
|
|
request = &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
joinCommunity(&s.Suite, community, s.owner, s.alice, request, "")
|
|
|
|
// Bob sends a message with mention
|
|
sentMessage = s.sendMention(s.bob, communityChat.ID)
|
|
|
|
// Check Alice gets the message and activity center notification
|
|
_, err = WaitOnMessengerResponse(
|
|
s.alice,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Messages()) == 1 && len(r.ActivityCenterNotifications()) == 1 &&
|
|
r.Messages()[0].ID == sentMessage.ID && r.ActivityCenterNotifications()[0].Message.ID == sentMessage.ID &&
|
|
r.ActivityCenterNotifications()[0].Type == ActivityCenterNotificationTypeMention
|
|
},
|
|
"no message for Alice",
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
// this test simulate the scenario, when we are leaving the community and after the leave
|
|
// receiving outdated COMMUNITY_REQUEST_TO_JOIN_RESPONSE and joining the community again
|
|
func (s *MessengerCommunitiesSuite) TestAliceDidNotProcessOutdatedCommunityRequestToJoinResponse() {
|
|
community, _ := s.createCommunity()
|
|
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, s.alice)
|
|
request := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
|
|
joinCommunity(&s.Suite, community, s.owner, s.alice, request, "")
|
|
|
|
response, err := s.alice.LeaveCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().False(response.Communities()[0].Joined())
|
|
|
|
// double-check that alice left the community
|
|
community, err = s.alice.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(community.Joined())
|
|
|
|
// prepare the same request to join response
|
|
community, err = s.owner.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
grant, err := community.BuildGrant(s.alice.IdentityPublicKey(), "")
|
|
s.Require().NoError(err)
|
|
|
|
var key *ecdsa.PrivateKey
|
|
if s.owner.transport.WakuVersion() == 2 {
|
|
key, err = s.owner.transport.RetrievePubsubTopicKey(community.PubsubTopic())
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
encryptedDescription, err := community.EncryptedDescription()
|
|
s.Require().NoError(err)
|
|
|
|
requestToJoinResponse := &protobuf.CommunityRequestToJoinResponse{
|
|
Clock: community.Clock(),
|
|
Accepted: true,
|
|
CommunityId: community.ID(),
|
|
Community: encryptedDescription,
|
|
Grant: grant,
|
|
ProtectedTopicPrivateKey: crypto.FromECDSA(key),
|
|
Shard: community.Shard().Protobuffer(),
|
|
}
|
|
|
|
// alice handle duplicated request to join response
|
|
state := &ReceivedMessageState{
|
|
Response: &MessengerResponse{},
|
|
CurrentMessageState: &CurrentMessageState{
|
|
PublicKey: community.ControlNode(),
|
|
},
|
|
}
|
|
|
|
err = s.alice.HandleCommunityRequestToJoinResponse(state, requestToJoinResponse, nil)
|
|
s.Require().Error(err, ErrOutdatedCommunityRequestToJoin)
|
|
|
|
// alice receives new request to join when she's already joined
|
|
requestToJoinResponse.Clock = requestToJoinResponse.Clock + 1
|
|
err = s.alice.HandleCommunityRequestToJoinResponse(state, requestToJoinResponse, nil)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesSuite) TestIgnoreOutdatedCommunityDescription() {
|
|
community, _ := s.createCommunity()
|
|
wrappedDescription1, err := community.ToProtocolMessageBytes()
|
|
s.Require().NoError(err)
|
|
signer, description1, err := communities.UnwrapCommunityDescriptionMessage(wrappedDescription1)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = community.AddMember(&s.alice.identity.PublicKey, []protobuf.CommunityMember_Roles{}, community.Clock())
|
|
s.Require().NoError(err)
|
|
wrappedDescription2, err := community.ToProtocolMessageBytes()
|
|
s.Require().NoError(err)
|
|
_, description2, err := communities.UnwrapCommunityDescriptionMessage(wrappedDescription2)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = community.AddMember(&s.bob.identity.PublicKey, []protobuf.CommunityMember_Roles{}, community.Clock())
|
|
s.Require().NoError(err)
|
|
wrappedDescription3, err := community.ToProtocolMessageBytes()
|
|
s.Require().NoError(err)
|
|
_, description3, err := communities.UnwrapCommunityDescriptionMessage(wrappedDescription3)
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().Less(description1.Clock, description2.Clock)
|
|
s.Require().Less(description2.Clock, description3.Clock)
|
|
|
|
// Handle first community description
|
|
{
|
|
messageState := s.bob.buildMessageState()
|
|
err = s.bob.handleCommunityDescription(messageState, signer, description1, wrappedDescription1, nil, community.Shard().Protobuffer())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(messageState.Response.Communities(), 1)
|
|
s.Require().Equal(description1.Clock, messageState.Response.Communities()[0].Clock())
|
|
}
|
|
|
|
// Handle third community description
|
|
{
|
|
messageState := s.bob.buildMessageState()
|
|
err = s.bob.handleCommunityDescription(messageState, signer, description3, wrappedDescription3, nil, community.Shard().Protobuffer())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(messageState.Response.Communities(), 1)
|
|
s.Require().Equal(description3.Clock, messageState.Response.Communities()[0].Clock())
|
|
|
|
communityFromDB, err := s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(description3.Clock, communityFromDB.Clock())
|
|
s.Require().Len(communityFromDB.Members(), 3)
|
|
}
|
|
|
|
// Handle second (out of order) community description
|
|
// It should be ignored
|
|
{
|
|
messageState := s.bob.buildMessageState()
|
|
err = s.bob.handleCommunityDescription(messageState, signer, description2, wrappedDescription2, nil, community.Shard().Protobuffer())
|
|
s.Require().Len(messageState.Response.Communities(), 0)
|
|
s.Require().Len(messageState.Response.CommunityChanges, 0)
|
|
s.Require().ErrorIs(err, communities.ErrInvalidCommunityDescriptionClockOutdated)
|
|
|
|
communityFromDB, err := s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(description3.Clock, communityFromDB.Clock())
|
|
s.Require().Len(communityFromDB.Members(), 3)
|
|
}
|
|
}
|