575 lines
17 KiB
Go
575 lines
17 KiB
Go
package protocol
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
)
|
|
|
|
var (
|
|
testMembershipUpdateMessageStruct = MembershipUpdateMessage{
|
|
ChatID: "chat-id",
|
|
Events: []MembershipUpdateEvent{
|
|
{
|
|
Type: protobuf.MembershipUpdateEvent_CHAT_CREATED,
|
|
Name: "thathata",
|
|
ChatID: "chat-id",
|
|
ClockValue: 156897373998501,
|
|
},
|
|
{
|
|
Type: protobuf.MembershipUpdateEvent_MEMBERS_ADDED,
|
|
Members: []string{"0x04aebe2bb01a988abe7d978662f21de7760486119876c680e5a559e38e086a2df6dad41c4e4d9079c03db3bced6cb70fca76afc5650e50ea19b81572046a813534"},
|
|
ChatID: "chat-id",
|
|
ClockValue: 156897373998502,
|
|
},
|
|
},
|
|
}
|
|
)
|
|
|
|
func TestSignMembershipUpdate(t *testing.T) {
|
|
key, err := crypto.HexToECDSA("838fbdd1b670209a258b90af25653a018bc582c44c56e6290a973eebbeb15732")
|
|
require.NoError(t, err)
|
|
event := &testMembershipUpdateMessageStruct.Events[0]
|
|
err = event.Sign(key)
|
|
require.NoError(t, err)
|
|
|
|
encodedEvent, err := proto.Marshal(event.ToProtobuf())
|
|
require.NoError(t, err)
|
|
|
|
var signatureMaterial []byte
|
|
signatureMaterial = append(signatureMaterial, []byte(testMembershipUpdateMessageStruct.ChatID)...)
|
|
signatureMaterial = crypto.Keccak256(append(signatureMaterial, encodedEvent...))
|
|
expected, err := crypto.Sign(signatureMaterial, key)
|
|
require.NoError(t, err)
|
|
require.Equal(t, encodedEvent, event.RawPayload)
|
|
require.Equal(t, expected, event.Signature)
|
|
|
|
// Sign the other event
|
|
err = testMembershipUpdateMessageStruct.Events[1].Sign(key)
|
|
require.NoError(t, err)
|
|
|
|
// Encode message
|
|
encodedMessage, err := testMembershipUpdateMessageStruct.ToProtobuf()
|
|
require.NoError(t, err)
|
|
// Verify it
|
|
verifiedMessage, err := MembershipUpdateMessageFromProtobuf(encodedMessage)
|
|
require.NoError(t, err)
|
|
require.Equal(t, verifiedMessage, &testMembershipUpdateMessageStruct)
|
|
}
|
|
|
|
func TestGroupCreator(t *testing.T) {
|
|
key, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
g, err := NewGroupWithCreator("abc", "#fa6565", 20, key)
|
|
require.NoError(t, err)
|
|
creator, err := g.Creator()
|
|
require.NoError(t, err)
|
|
require.Equal(t, publicKeyToString(&key.PublicKey), creator)
|
|
}
|
|
|
|
func TestGroupProcessEvent(t *testing.T) {
|
|
createGroup := func(admins, members, joined []string, name string, color string, image string) Group {
|
|
return Group{
|
|
name: name,
|
|
color: color,
|
|
image: []byte(image),
|
|
admins: newStringSetFromSlice(admins),
|
|
members: newStringSetFromSlice(members),
|
|
}
|
|
}
|
|
|
|
const emptyName = ""
|
|
const emptyColor = ""
|
|
const emptyImage = ""
|
|
|
|
testCases := []struct {
|
|
Name string
|
|
Group Group
|
|
Result Group
|
|
From string
|
|
Event MembershipUpdateEvent
|
|
}{
|
|
{
|
|
Name: "chat-created event",
|
|
Group: createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup([]string{"0xabc"}, []string{"0xabc"}, []string{"0xabc"}, "some-name", "#7cda00", emptyImage),
|
|
From: "0xabc",
|
|
Event: NewChatCreatedEvent("some-name", "#7cda00", 0),
|
|
},
|
|
{
|
|
Name: "name-changed event",
|
|
Group: createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup(nil, nil, nil, "some-name", emptyColor, emptyImage),
|
|
From: "0xabc",
|
|
Event: NewNameChangedEvent("some-name", 0),
|
|
},
|
|
{
|
|
Name: "color-changed event",
|
|
Group: createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup(nil, nil, nil, emptyName, "#7cda00", emptyImage),
|
|
From: "0xabc",
|
|
Event: NewColorChangedEvent("#7cda00", 0),
|
|
},
|
|
{
|
|
Name: "image-changed event",
|
|
Group: createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup(nil, nil, nil, emptyName, emptyColor, "123"),
|
|
From: "0xabc",
|
|
Event: NewImageChangedEvent([]byte("123"), 0),
|
|
},
|
|
{
|
|
Name: "admins-added event",
|
|
Group: createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup([]string{"0xabc", "0x123"}, nil, nil, emptyName, emptyColor, emptyImage),
|
|
From: "0xabc",
|
|
Event: NewAdminsAddedEvent([]string{"0xabc", "0x123"}, 0),
|
|
},
|
|
{
|
|
Name: "admin-removed event",
|
|
Group: createGroup([]string{"0xabc", "0xdef"}, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup([]string{"0xdef"}, nil, nil, emptyName, emptyColor, emptyImage),
|
|
From: "0xabc",
|
|
Event: NewAdminRemovedEvent("0xabc", 0),
|
|
},
|
|
{
|
|
Name: "members-added event",
|
|
Group: createGroup(nil, nil, nil, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup(nil, []string{"0xabc", "0xdef"}, nil, emptyName, emptyColor, emptyImage),
|
|
From: "0xabc",
|
|
Event: NewMembersAddedEvent([]string{"0xabc", "0xdef"}, 0),
|
|
},
|
|
{
|
|
Name: "member-removed event",
|
|
Group: createGroup(nil, []string{"0xabc", "0xdef"}, []string{"0xdef", "0xabc"}, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup(nil, []string{"0xdef"}, []string{"0xdef"}, emptyName, emptyColor, emptyImage),
|
|
From: "0xabc",
|
|
Event: NewMemberRemovedEvent("0xabc", 0),
|
|
},
|
|
{
|
|
Name: "member-joined event",
|
|
Group: createGroup(nil, []string{"0xabc", "0xdef"}, []string{"0xabc"}, emptyName, emptyColor, emptyImage),
|
|
Result: createGroup(nil, []string{"0xabc", "0xdef"}, []string{"0xabc", "0xdef"}, emptyName, emptyColor, emptyImage),
|
|
From: "0xdef",
|
|
Event: NewMemberJoinedEvent(0),
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
g := tc.Group
|
|
tc.Event.From = tc.From
|
|
g.processEvent(tc.Event)
|
|
require.EqualValues(t, tc.Result, g)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGroupValidateEvent(t *testing.T) {
|
|
createGroup := func(admins, members []string) Group {
|
|
return Group{
|
|
admins: newStringSetFromSlice(admins),
|
|
members: newStringSetFromSlice(members),
|
|
}
|
|
}
|
|
testCases := []struct {
|
|
Name string
|
|
From string
|
|
Group Group
|
|
Event MembershipUpdateEvent
|
|
Result bool
|
|
}{
|
|
{
|
|
Name: "chat-created with empty admins and members",
|
|
Group: createGroup(nil, nil),
|
|
From: "0xabc",
|
|
Event: NewChatCreatedEvent("test", "#fa6565", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "chat-created with existing admins",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
From: "0xabc",
|
|
Event: NewChatCreatedEvent("test", "#fa6565", 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "chat-created with existing members",
|
|
Group: createGroup(nil, []string{"0xabc"}),
|
|
From: "0xabc",
|
|
Event: NewChatCreatedEvent("test", "#fa6565", 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "name-changed allowed because from is admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
Event: NewNameChangedEvent("new-name", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "name-changed allowed because from is member",
|
|
From: "0x123",
|
|
Group: createGroup([]string{"0xabc"}, []string{"0x123"}),
|
|
Event: NewNameChangedEvent("new-name", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "color-changed allowed because from is admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
Event: NewColorChangedEvent("#7cda00", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "color-changed allowed because from is member",
|
|
From: "0x123",
|
|
Group: createGroup([]string{"0xabc"}, []string{"0x123"}),
|
|
Event: NewColorChangedEvent("#7cda00", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "image-changed allowed because from is admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
Event: NewImageChangedEvent([]byte{1, 2, 3}, 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "image-changed not allowed for non-admins",
|
|
From: "0xabc",
|
|
Group: createGroup(nil, nil),
|
|
Event: NewImageChangedEvent([]byte{1, 2, 3}, 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "members-added allowed because from is admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
Event: NewMembersAddedEvent([]string{"0x123"}, 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "members-added not allowed because from is member",
|
|
From: "0x123",
|
|
Group: createGroup([]string{"0xabc"}, []string{"0x123"}),
|
|
Event: NewMembersAddedEvent([]string{"0x123"}, 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "member-removed allowed because removing themselves",
|
|
From: "0xabc",
|
|
Group: createGroup(nil, nil),
|
|
Event: NewMemberRemovedEvent("0xabc", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "member-removed allowed because from is admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc"}, []string{"0x123"}),
|
|
Event: NewMemberRemovedEvent("0x123", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "member-removed not allowed for non-admins",
|
|
From: "0x123",
|
|
Group: createGroup([]string{"0xabc"}, []string{"0x123", "0x456"}),
|
|
Event: NewMemberRemovedEvent("0x456", 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "member-joined must be in members",
|
|
From: "0xabc",
|
|
Group: createGroup(nil, []string{"0xabc"}),
|
|
Event: NewMemberJoinedEvent(0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "member-joined not valid because not in members",
|
|
From: "0xabc",
|
|
Group: createGroup(nil, nil),
|
|
Event: NewMemberJoinedEvent(0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "member-joined not valid because from differs from the event",
|
|
From: "0xdef",
|
|
Group: createGroup(nil, nil),
|
|
Event: NewMemberJoinedEvent(0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "admins-added allowed because originating from other admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc", "0x123"}, []string{"0xdef", "0xghi"}),
|
|
Event: NewAdminsAddedEvent([]string{"0xdef"}, 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "admins-added not allowed because not from admin",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0x123"}, []string{"0xdef", "0xghi"}),
|
|
Event: NewAdminsAddedEvent([]string{"0xdef"}, 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "admins-added not allowed because not in members",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc", "0x123"}, []string{"0xghi"}),
|
|
Event: NewAdminsAddedEvent([]string{"0xdef"}, 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "admin-removed allowed because is admin and removes themselves",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
Event: NewAdminRemovedEvent("0xabc", 0),
|
|
Result: true,
|
|
},
|
|
{
|
|
Name: "admin-removed not allowed because not themselves",
|
|
From: "0xabc",
|
|
Group: createGroup([]string{"0xabc", "0xdef"}, nil),
|
|
Event: NewAdminRemovedEvent("0xdef", 0),
|
|
Result: false,
|
|
},
|
|
{
|
|
Name: "admin-removed not allowed because not admin",
|
|
From: "0xdef",
|
|
Group: createGroup([]string{"0xabc"}, nil),
|
|
Event: NewAdminRemovedEvent("0xabc", 0),
|
|
Result: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
tc.Event.From = tc.From
|
|
result := tc.Group.validateEvent(tc.Event)
|
|
assert.Equal(t, tc.Result, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMembershipUpdateEventEqual(t *testing.T) {
|
|
u1 := MembershipUpdateEvent{
|
|
Type: protobuf.MembershipUpdateEvent_CHAT_CREATED,
|
|
ClockValue: 1,
|
|
Members: []string{"0xabc"},
|
|
Name: "abc",
|
|
Signature: []byte("signature"),
|
|
}
|
|
require.True(t, u1.Equal(u1))
|
|
|
|
// Verify equality breaking.
|
|
u2 := u1
|
|
u2.Signature = []byte("different-signature")
|
|
require.False(t, u1.Equal(u2))
|
|
}
|
|
|
|
func TestAbridgedEventsNameChanged(t *testing.T) {
|
|
var clock uint64 = 0
|
|
creator, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
creatorID := publicKeyToString(&creator.PublicKey)
|
|
|
|
g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
// Full events is only a single one
|
|
require.Len(t, g.Events(), 1)
|
|
// same as abridged
|
|
require.Len(t, g.AbridgedEvents(), 1)
|
|
|
|
// We change name of the chat
|
|
nameChangedEvent1 := NewNameChangedEvent("name-1", clock)
|
|
nameChangedEvent1.From = creatorID
|
|
nameChangedEvent1.ChatID = g.chatID
|
|
err = g.ProcessEvent(nameChangedEvent1)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
// We change name of the chat again
|
|
nameChangedEvent2 := NewNameChangedEvent("name-2", clock)
|
|
nameChangedEvent2.From = creatorID
|
|
nameChangedEvent2.ChatID = g.chatID
|
|
err = g.ProcessEvent(nameChangedEvent2)
|
|
require.NoError(t, err)
|
|
|
|
// Full events is 3 events
|
|
require.Len(t, g.Events(), 3)
|
|
// While abridged should exclude the first name-1 event
|
|
require.Len(t, g.AbridgedEvents(), 2)
|
|
require.Equal(t, g.AbridgedEvents()[1].Name, "name-2")
|
|
}
|
|
|
|
func TestAbridgedEventsMembers(t *testing.T) {
|
|
var clock uint64 = 0
|
|
creator, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
creatorID := publicKeyToString(&creator.PublicKey)
|
|
|
|
member1, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member1ID := publicKeyToString(&member1.PublicKey)
|
|
|
|
member2, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member2ID := publicKeyToString(&member2.PublicKey)
|
|
|
|
member3, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member3ID := publicKeyToString(&member3.PublicKey)
|
|
|
|
member4, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member4ID := publicKeyToString(&member4.PublicKey)
|
|
|
|
g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
// Full events is only a single one
|
|
require.Len(t, g.Events(), 1)
|
|
// same as abridged
|
|
require.Len(t, g.AbridgedEvents(), 1)
|
|
|
|
// Add three new members
|
|
event := NewMembersAddedEvent([]string{member1ID, member2ID, member3ID}, clock)
|
|
event.From = creatorID
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
require.Len(t, g.Events(), 2)
|
|
// All the events are relevant here, so it should be the same
|
|
require.Len(t, g.AbridgedEvents(), 2)
|
|
|
|
// We remove one of the users
|
|
event = NewMemberRemovedEvent(member3ID, clock)
|
|
event.From = creatorID
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
require.Len(t, g.Events(), 3)
|
|
// All the events are relevant here, so it should be the same
|
|
require.Len(t, g.AbridgedEvents(), 3)
|
|
|
|
// Add a new member
|
|
event = NewMembersAddedEvent([]string{member4ID}, clock)
|
|
event.From = creatorID
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
require.Len(t, g.Events(), 4)
|
|
// All the events are relevant here, so it should be the same
|
|
require.Len(t, g.AbridgedEvents(), 4)
|
|
|
|
// We remove the member just added
|
|
event = NewMemberRemovedEvent(member4ID, clock)
|
|
event.From = creatorID
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, g.Events(), 5)
|
|
// The previous two events, should be removed, because they have no impact
|
|
// on the chat history
|
|
abridgedEvents := g.AbridgedEvents()
|
|
require.Len(t, abridgedEvents, 3)
|
|
|
|
require.Equal(t, uint64(0), abridgedEvents[0].ClockValue)
|
|
require.Equal(t, uint64(1), abridgedEvents[1].ClockValue)
|
|
require.Equal(t, uint64(2), abridgedEvents[2].ClockValue)
|
|
}
|
|
|
|
func TestAbridgedEventsAdmins(t *testing.T) {
|
|
var clock uint64 = 0
|
|
creator, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
creatorID := publicKeyToString(&creator.PublicKey)
|
|
|
|
member1, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member1ID := publicKeyToString(&member1.PublicKey)
|
|
|
|
member2, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member2ID := publicKeyToString(&member2.PublicKey)
|
|
|
|
member3, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
member3ID := publicKeyToString(&member3.PublicKey)
|
|
|
|
g, err := NewGroupWithCreator("name-0", "#fa6565", clock, creator)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
// Full events is only a single one
|
|
require.Len(t, g.Events(), 1)
|
|
// same as abridged
|
|
require.Len(t, g.AbridgedEvents(), 1)
|
|
|
|
// Add three new members
|
|
event := NewMembersAddedEvent([]string{member1ID, member2ID, member3ID}, clock)
|
|
event.From = creatorID
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
clock++
|
|
|
|
require.Len(t, g.Events(), 2)
|
|
// All the events are relevant here, so it should be the same
|
|
require.Len(t, g.AbridgedEvents(), 2)
|
|
|
|
// Make two of them admins
|
|
event = NewAdminsAddedEvent([]string{member1ID, member2ID}, clock)
|
|
event.From = creatorID
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
|
|
require.Len(t, g.Events(), 3)
|
|
// All the events are relevant here, so it should be the same
|
|
require.Len(t, g.AbridgedEvents(), 3)
|
|
}
|
|
|
|
func TestWasEverMember(t *testing.T) {
|
|
key, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
g, err := NewGroupWithCreator("abc", "#fa6565", 20, key)
|
|
require.NoError(t, err)
|
|
|
|
wasMember, err := g.WasEverMember(publicKeyToString(&key.PublicKey))
|
|
require.NoError(t, err)
|
|
require.True(t, wasMember)
|
|
|
|
key2, err := crypto.GenerateKey()
|
|
require.NoError(t, err)
|
|
|
|
wasMember, err = g.WasEverMember(publicKeyToString(&key2.PublicKey))
|
|
require.NoError(t, err)
|
|
require.False(t, wasMember)
|
|
|
|
// Add a new member
|
|
event := NewMembersAddedEvent([]string{publicKeyToString(&key2.PublicKey)}, 21)
|
|
event.From = publicKeyToString(&key.PublicKey)
|
|
event.ChatID = g.chatID
|
|
err = g.ProcessEvent(event)
|
|
require.NoError(t, err)
|
|
|
|
wasMember, err = g.WasEverMember(publicKeyToString(&key2.PublicKey))
|
|
require.NoError(t, err)
|
|
require.True(t, wasMember)
|
|
}
|