mirror of
https://github.com/status-im/status-go.git
synced 2025-01-09 22:26:30 +00:00
b8b402da8d
This commit adds new tables to the database and APIs in `Messenger` and communities `Manager` to store `CheckChannelPermissionsResponse`s. The responses are stored whenever channel permissions have been checked. The reason we're doing this is so that clients can retrieve the last known channel permission state before waiting for onchain checks to finish.
465 lines
14 KiB
Go
465 lines
14 KiB
Go
package communities
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"database/sql"
|
|
"io/ioutil"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/status-im/status-go/appdatabase"
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/protocol/common"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
"github.com/status-im/status-go/protocol/sqlite"
|
|
)
|
|
|
|
func TestPersistenceSuite(t *testing.T) {
|
|
suite.Run(t, new(PersistenceSuite))
|
|
}
|
|
|
|
type PersistenceSuite struct {
|
|
suite.Suite
|
|
|
|
db *Persistence
|
|
}
|
|
|
|
func (s *PersistenceSuite) SetupTest() {
|
|
s.db = nil
|
|
|
|
dbPath, err := ioutil.TempFile("", "")
|
|
s.NoError(err, "creating temp file for db")
|
|
|
|
db, err := appdatabase.InitializeDB(dbPath.Name(), "", sqlite.ReducedKDFIterationsNumber)
|
|
s.NoError(err, "creating sqlite db instance")
|
|
|
|
err = sqlite.Migrate(db)
|
|
s.NoError(err, "protocol migrate")
|
|
|
|
s.db = &Persistence{db: db}
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestSaveCommunity() {
|
|
id, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
|
|
// there is one community inserted by default
|
|
communities, err := s.db.AllCommunities(&id.PublicKey)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(communities, 1)
|
|
|
|
community := Community{
|
|
config: &Config{
|
|
PrivateKey: id,
|
|
ID: &id.PublicKey,
|
|
Joined: true,
|
|
Spectated: true,
|
|
Verified: true,
|
|
CommunityDescription: &protobuf.CommunityDescription{},
|
|
},
|
|
}
|
|
s.Require().NoError(s.db.SaveCommunity(&community))
|
|
|
|
communities, err = s.db.AllCommunities(&id.PublicKey)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(communities, 2)
|
|
s.Equal(types.HexBytes(crypto.CompressPubkey(&id.PublicKey)), communities[1].ID())
|
|
s.Equal(true, communities[1].Joined())
|
|
s.Equal(true, communities[1].Spectated())
|
|
s.Equal(true, communities[1].Verified())
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestShouldHandleSyncCommunity() {
|
|
sc := &protobuf.SyncCommunity{
|
|
Id: []byte("0x123456"),
|
|
PrivateKey: []byte("0xfedcba"),
|
|
Description: []byte("this is a description"),
|
|
Joined: true,
|
|
Verified: true,
|
|
Clock: uint64(time.Now().Unix()),
|
|
}
|
|
|
|
// check an empty db to see if a community should be synced
|
|
should, err := s.db.ShouldHandleSyncCommunity(sc)
|
|
s.NoError(err, "SaveSyncCommunity")
|
|
s.True(should)
|
|
|
|
// add a new community to the db
|
|
err = s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
|
|
s.NoError(err, "saveRawCommunityRow")
|
|
|
|
rcrs, err := s.db.getAllCommunitiesRaw()
|
|
s.NoError(err, "should have no error from getAllCommunitiesRaw")
|
|
s.Len(rcrs, 2, "length of all communities raw should be 2")
|
|
|
|
// check again to see is the community should be synced
|
|
sc.Clock--
|
|
should, err = s.db.ShouldHandleSyncCommunity(sc)
|
|
s.NoError(err, "SaveSyncCommunity")
|
|
s.False(should)
|
|
|
|
// check again to see is the community should be synced
|
|
sc.Clock++
|
|
sc.Clock++
|
|
should, err = s.db.ShouldHandleSyncCommunity(sc)
|
|
s.NoError(err, "SaveSyncCommunity")
|
|
s.True(should)
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestSetSyncClock() {
|
|
sc := &protobuf.SyncCommunity{
|
|
Id: []byte("0x123456"),
|
|
PrivateKey: []byte("0xfedcba"),
|
|
Description: []byte("this is a description"),
|
|
Joined: true,
|
|
Verified: true,
|
|
}
|
|
|
|
// add a new community to the db
|
|
err := s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
|
|
s.NoError(err, "saveRawCommunityRow")
|
|
|
|
// retrieve row from db synced_at must be zero
|
|
rcr, err := s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.Zero(rcr.SyncedAt, "synced_at must be zero value")
|
|
|
|
// Set the synced_at value
|
|
clock := uint64(time.Now().Unix())
|
|
err = s.db.SetSyncClock(sc.Id, clock)
|
|
s.NoError(err, "SetSyncClock")
|
|
|
|
// Retrieve row from db and check clock matches synced_at value
|
|
rcr, err = s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.Equal(clock, rcr.SyncedAt, "synced_at must equal the value of the clock")
|
|
|
|
// Set Synced At with an older clock value
|
|
olderClock := clock - uint64(256)
|
|
err = s.db.SetSyncClock(sc.Id, olderClock)
|
|
s.NoError(err, "SetSyncClock")
|
|
|
|
// Retrieve row from db and check olderClock matches synced_at value
|
|
rcr, err = s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.NotEqual(olderClock, rcr.SyncedAt, "synced_at must not equal the value of the olderClock value")
|
|
|
|
// Set Synced At with a newer clock value
|
|
newerClock := clock + uint64(512)
|
|
err = s.db.SetSyncClock(sc.Id, newerClock)
|
|
s.NoError(err, "SetSyncClock")
|
|
|
|
// Retrieve row from db and check olderClock matches synced_at value
|
|
rcr, err = s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.Equal(newerClock, rcr.SyncedAt, "synced_at must equal the value of the newerClock value")
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestSetPrivateKey() {
|
|
sc := &protobuf.SyncCommunity{
|
|
Id: []byte("0x123456"),
|
|
Description: []byte("this is a description"),
|
|
Joined: true,
|
|
Verified: true,
|
|
}
|
|
|
|
// add a new community to the db with no private key
|
|
err := s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
|
|
s.NoError(err, "saveRawCommunityRow")
|
|
|
|
// retrieve row from db, private key must be zero
|
|
rcr, err := s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.Zero(rcr.PrivateKey, "private key must be zero value")
|
|
|
|
// Set private key
|
|
pk, err := crypto.GenerateKey()
|
|
s.NoError(err, "crypto.GenerateKey")
|
|
err = s.db.SetPrivateKey(sc.Id, pk)
|
|
s.NoError(err, "SetPrivateKey")
|
|
|
|
// retrieve row from db again, private key must match the given key
|
|
rcr, err = s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.Equal(crypto.FromECDSA(pk), rcr.PrivateKey, "private key must match given key")
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestJoinedAndPendingCommunitiesWithRequests() {
|
|
identity, err := crypto.GenerateKey()
|
|
s.NoError(err, "crypto.GenerateKey shouldn't give any error")
|
|
|
|
clock := uint64(time.Now().Unix())
|
|
|
|
// Add a new community that we have joined
|
|
com := s.makeNewCommunity(identity)
|
|
com.Join()
|
|
sc, err := com.ToSyncCommunityProtobuf(clock, nil)
|
|
s.NoError(err, "Community.ToSyncCommunityProtobuf shouldn't give any error")
|
|
err = s.db.saveRawCommunityRow(fromSyncCommunityProtobuf(sc))
|
|
s.NoError(err, "saveRawCommunityRow")
|
|
|
|
// Add a new community that we have requested to join, but not yet joined
|
|
com2 := s.makeNewCommunity(identity)
|
|
err = s.db.SaveCommunity(com2)
|
|
s.NoError(err, "SaveCommunity shouldn't give any error")
|
|
|
|
rtj := &RequestToJoin{
|
|
ID: types.HexBytes{1, 2, 3, 4, 5, 6, 7, 8},
|
|
PublicKey: common.PubkeyToHex(&identity.PublicKey),
|
|
Clock: clock,
|
|
CommunityID: com2.ID(),
|
|
State: RequestToJoinStatePending,
|
|
}
|
|
err = s.db.SaveRequestToJoin(rtj)
|
|
s.NoError(err, "SaveRequestToJoin shouldn't give any error")
|
|
|
|
comms, err := s.db.JoinedAndPendingCommunitiesWithRequests(&identity.PublicKey)
|
|
s.NoError(err, "JoinedAndPendingCommunitiesWithRequests shouldn't give any error")
|
|
s.Len(comms, 2, "Should have 2 communities")
|
|
|
|
for _, comm := range comms {
|
|
switch comm.IDString() {
|
|
case com.IDString():
|
|
s.Len(comm.RequestsToJoin(), 0, "Should have no RequestsToJoin")
|
|
case com2.IDString():
|
|
rtjs := comm.RequestsToJoin()
|
|
s.Len(rtjs, 1, "Should have one RequestsToJoin")
|
|
s.Equal(rtjs[0], rtj, "RequestToJoin should match the Request stored in the db")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestSaveRequestToLeave() {
|
|
rtl := &RequestToLeave{
|
|
ID: []byte("0x123456"),
|
|
PublicKey: "0xffffff",
|
|
Clock: 2,
|
|
CommunityID: []byte("0x654321"),
|
|
}
|
|
|
|
err := s.db.SaveRequestToLeave(rtl)
|
|
s.NoError(err)
|
|
|
|
// older clocks should not be saved
|
|
rtl.Clock = 1
|
|
err = s.db.SaveRequestToLeave(rtl)
|
|
s.Error(err)
|
|
}
|
|
|
|
func (s *PersistenceSuite) makeNewCommunity(identity *ecdsa.PrivateKey) *Community {
|
|
comPrivKey, err := crypto.GenerateKey()
|
|
s.NoError(err, "crypto.GenerateKey shouldn't give any error")
|
|
|
|
com, err := New(Config{
|
|
MemberIdentity: &identity.PublicKey,
|
|
PrivateKey: comPrivKey,
|
|
ID: &comPrivKey.PublicKey,
|
|
})
|
|
s.NoError(err, "New shouldn't give any error")
|
|
|
|
md, err := com.MarshaledDescription()
|
|
s.NoError(err, "Community.MarshaledDescription shouldn't give any error")
|
|
com.config.MarshaledCommunityDescription = md
|
|
|
|
return com
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestGetSyncedRawCommunity() {
|
|
sc := &protobuf.SyncCommunity{
|
|
Id: []byte("0x123456"),
|
|
PrivateKey: []byte("0xfedcba"),
|
|
Description: []byte("this is a description"),
|
|
Joined: true,
|
|
Verified: true,
|
|
Spectated: true,
|
|
}
|
|
|
|
// add a new community to the db
|
|
err := s.db.saveRawCommunityRowWithoutSyncedAt(fromSyncCommunityProtobuf(sc))
|
|
s.NoError(err, "saveRawCommunityRow")
|
|
|
|
// retrieve row from db synced_at must be zero
|
|
rcr, err := s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.Zero(rcr.SyncedAt, "synced_at must be zero value")
|
|
|
|
// retrieve synced row from db, should fail
|
|
src, err := s.db.getSyncedRawCommunity(sc.Id)
|
|
s.EqualError(err, sql.ErrNoRows.Error())
|
|
s.Nil(src)
|
|
|
|
// Set the synced_at value
|
|
clock := uint64(time.Now().Unix())
|
|
err = s.db.SetSyncClock(sc.Id, clock)
|
|
s.NoError(err, "SetSyncClock")
|
|
|
|
// retrieve row from db synced_at must not be zero
|
|
rcr, err = s.db.getRawCommunityRow(sc.Id)
|
|
s.NoError(err, "getRawCommunityRow")
|
|
s.NotZero(rcr.SyncedAt, "synced_at must be zero value")
|
|
|
|
// retrieve synced row from db, should succeed
|
|
src, err = s.db.getSyncedRawCommunity(sc.Id)
|
|
s.NoError(err)
|
|
s.NotNil(src)
|
|
s.Equal(clock, src.SyncedAt)
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestGetCommunitiesSettings() {
|
|
settings := []CommunitySettings{
|
|
{CommunityID: "0x01", HistoryArchiveSupportEnabled: false},
|
|
{CommunityID: "0x02", HistoryArchiveSupportEnabled: true},
|
|
{CommunityID: "0x03", HistoryArchiveSupportEnabled: false},
|
|
}
|
|
|
|
for i := range settings {
|
|
stg := settings[i]
|
|
err := s.db.SaveCommunitySettings(stg)
|
|
s.NoError(err)
|
|
}
|
|
|
|
rst, err := s.db.GetCommunitiesSettings()
|
|
s.NoError(err)
|
|
s.Equal(settings, rst)
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestSaveCommunitySettings() {
|
|
settings := CommunitySettings{CommunityID: "0x01", HistoryArchiveSupportEnabled: false}
|
|
err := s.db.SaveCommunitySettings(settings)
|
|
s.NoError(err)
|
|
rst, err := s.db.GetCommunitiesSettings()
|
|
s.NoError(err)
|
|
s.Equal(1, len(rst))
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestDeleteCommunitySettings() {
|
|
settings := CommunitySettings{CommunityID: "0x01", HistoryArchiveSupportEnabled: false}
|
|
|
|
err := s.db.SaveCommunitySettings(settings)
|
|
s.NoError(err)
|
|
|
|
rst, err := s.db.GetCommunitiesSettings()
|
|
s.NoError(err)
|
|
s.Equal(1, len(rst))
|
|
s.NoError(s.db.DeleteCommunitySettings(types.HexBytes{0x01}))
|
|
rst2, err := s.db.GetCommunitiesSettings()
|
|
s.NoError(err)
|
|
s.Equal(0, len(rst2))
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestUpdateCommunitySettings() {
|
|
settings := []CommunitySettings{
|
|
{CommunityID: "0x01", HistoryArchiveSupportEnabled: true},
|
|
{CommunityID: "0x02", HistoryArchiveSupportEnabled: false},
|
|
}
|
|
|
|
s.NoError(s.db.SaveCommunitySettings(settings[0]))
|
|
s.NoError(s.db.SaveCommunitySettings(settings[1]))
|
|
|
|
settings[0].HistoryArchiveSupportEnabled = true
|
|
settings[1].HistoryArchiveSupportEnabled = false
|
|
|
|
s.NoError(s.db.UpdateCommunitySettings(settings[0]))
|
|
s.NoError(s.db.UpdateCommunitySettings(settings[1]))
|
|
|
|
rst, err := s.db.GetCommunitiesSettings()
|
|
s.NoError(err)
|
|
s.Equal(settings, rst)
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestGetCommunityTokens() {
|
|
tokens, err := s.db.GetCommunityTokens("123")
|
|
s.Require().NoError(err)
|
|
s.Require().Len(tokens, 0)
|
|
|
|
token := CommunityToken{
|
|
CommunityID: "123",
|
|
TokenType: protobuf.CommunityTokenType_ERC721,
|
|
Address: "0x123",
|
|
Name: "StatusToken",
|
|
Symbol: "STT",
|
|
Description: "desc",
|
|
Supply: 123,
|
|
InfiniteSupply: false,
|
|
Transferable: true,
|
|
RemoteSelfDestruct: true,
|
|
ChainID: 1,
|
|
DeployState: InProgress,
|
|
Base64Image: "ABCD",
|
|
}
|
|
|
|
token2 := CommunityToken{
|
|
CommunityID: "345",
|
|
TokenType: protobuf.CommunityTokenType_ERC721,
|
|
Address: "0x345",
|
|
Name: "StatusToken",
|
|
Symbol: "STT",
|
|
Description: "desc",
|
|
Supply: 345,
|
|
InfiniteSupply: false,
|
|
Transferable: true,
|
|
RemoteSelfDestruct: true,
|
|
ChainID: 2,
|
|
DeployState: Failed,
|
|
Base64Image: "QWERTY",
|
|
}
|
|
|
|
err = s.db.AddCommunityToken(&token)
|
|
s.Require().NoError(err)
|
|
err = s.db.AddCommunityToken(&token2)
|
|
s.Require().NoError(err)
|
|
|
|
tokens, err = s.db.GetCommunityTokens("123")
|
|
s.Require().NoError(err)
|
|
s.Require().Len(tokens, 1)
|
|
s.Require().Equal(token, *tokens[0])
|
|
|
|
err = s.db.UpdateCommunityTokenState(1, "0x123", Deployed)
|
|
s.Require().NoError(err)
|
|
tokens, err = s.db.GetCommunityTokens("123")
|
|
s.Require().NoError(err)
|
|
s.Require().Len(tokens, 1)
|
|
s.Require().Equal(Deployed, tokens[0].DeployState)
|
|
}
|
|
|
|
func (s *PersistenceSuite) TestSaveCheckChannelPermissionResponse() {
|
|
|
|
viewAndPostPermissionResults := make(map[string]*PermissionTokenCriteriaResult)
|
|
viewAndPostPermissionResults["one"] = &PermissionTokenCriteriaResult{
|
|
Criteria: []bool{true, true, true, true},
|
|
}
|
|
viewAndPostPermissionResults["two"] = &PermissionTokenCriteriaResult{
|
|
Criteria: []bool{false},
|
|
}
|
|
chatID := "some-chat-id"
|
|
communityID := "some-community-id"
|
|
|
|
checkChannelPermissionResponse := &CheckChannelPermissionsResponse{
|
|
ViewOnlyPermissions: &CheckChannelViewOnlyPermissionsResult{
|
|
Satisfied: true,
|
|
Permissions: make(map[string]*PermissionTokenCriteriaResult),
|
|
},
|
|
ViewAndPostPermissions: &CheckChannelViewAndPostPermissionsResult{
|
|
Satisfied: true,
|
|
Permissions: viewAndPostPermissionResults,
|
|
},
|
|
}
|
|
|
|
err := s.db.SaveCheckChannelPermissionResponse(communityID, chatID, checkChannelPermissionResponse)
|
|
s.NoError(err)
|
|
|
|
responses, err := s.db.GetCheckChannelPermissionResponses(communityID)
|
|
s.NoError(err)
|
|
s.Require().Len(responses, 1)
|
|
s.Require().NotNil(responses[chatID])
|
|
s.Require().True(responses[chatID].ViewOnlyPermissions.Satisfied)
|
|
s.Require().Len(responses[chatID].ViewOnlyPermissions.Permissions, 0)
|
|
s.Require().True(responses[chatID].ViewAndPostPermissions.Satisfied)
|
|
s.Require().Len(responses[chatID].ViewAndPostPermissions.Permissions, 2)
|
|
s.Require().Equal(responses[chatID].ViewAndPostPermissions.Permissions["one"].Criteria, []bool{true, true, true, true})
|
|
s.Require().Equal(responses[chatID].ViewAndPostPermissions.Permissions["two"].Criteria, []bool{false})
|
|
}
|