status-go/protocol/contact_test.go

784 lines
23 KiB
Go

package protocol
import (
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
type contactTest struct {
actualLocalState ContactRequestState
expectedLocalState ContactRequestState
actualRemoteState ContactRequestState
expectedRemoteState ContactRequestState
expectedAdded bool
expectedHasAddedUs bool
expectedMutual bool
}
func (ct contactTest) Contact() Contact {
return Contact{
ContactRequestLocalState: ct.actualLocalState,
ContactRequestRemoteState: ct.actualRemoteState,
}
}
func validateContactTest(t *testing.T, contact Contact, tc contactTest, testNum int) {
failedMessage := fmt.Sprintf("test failed: %d", testNum)
require.Equal(t, tc.expectedLocalState, contact.ContactRequestLocalState, failedMessage+", contact request local state not matching")
require.Equal(t, tc.expectedRemoteState, contact.ContactRequestRemoteState, failedMessage+", contact request remote state not matching")
require.Equal(t, tc.expectedAdded, contact.added(), failedMessage+", added() not matching")
require.Equal(t, tc.expectedHasAddedUs, contact.hasAddedUs(), failedMessage+", hasAddedUs() not matching")
require.Equal(t, tc.expectedMutual, contact.mutual(), failedMessage+", mutual() not matching")
}
/*
none/none
sent/none
dismissed/none
none/received
sent/received
dismissed/received
*/
func TestContactContactRequestSent(t *testing.T) {
clock := uint64(1)
/* Cases to consider are:
Local = none Remote = none
Local = none Remote = received
Local = sent Remote = none
Local = sent Remote = received
Local = dismissed Remote = none
Local = dismissed Remote = received
*/
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: true,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: true,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: true,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.ContactRequestSent(clock)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestContactAcceptContactRequest(t *testing.T) {
clock := uint64(1)
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: true,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: true,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: true,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.AcceptContactRequest(clock)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestContactRetractContactRequest(t *testing.T) {
clock := uint64(1)
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.RetractContactRequest(clock)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestContactDismissContactRequest(t *testing.T) {
clock := uint64(1)
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.DismissContactRequest(clock)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestContactContactRequestRetracted(t *testing.T) {
clock := uint64(1)
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateNone,
expectedAdded: false,
expectedHasAddedUs: false,
expectedMutual: false,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.ContactRequestRetracted(clock, false)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestContactContactRequestReceived(t *testing.T) {
clock := uint64(1)
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.ContactRequestReceived(clock)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestContactContactRequestAccepted(t *testing.T) {
clock := uint64(1)
tests := []contactTest{
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateNone,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateNone,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateSent,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateSent,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: true,
expectedHasAddedUs: true,
expectedMutual: true,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateNone,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
{
actualLocalState: ContactRequestStateDismissed,
actualRemoteState: ContactRequestStateReceived,
expectedLocalState: ContactRequestStateDismissed,
expectedRemoteState: ContactRequestStateReceived,
expectedAdded: false,
expectedHasAddedUs: true,
expectedMutual: false,
},
}
for testNum, tc := range tests {
contact := tc.Contact()
contact.ContactRequestAccepted(clock)
validateContactTest(t, contact, tc, testNum+1)
}
}
func TestMarshalContactJSON(t *testing.T) {
contact := &Contact{
LocalNickname: "primary-name",
Alias: "secondary-name",
ContactRequestLocalState: ContactRequestStateSent,
ContactRequestRemoteState: ContactRequestStateReceived,
}
id, err := crypto.GenerateKey()
require.NoError(t, err)
contact.ID = common.PubkeyToHex(&id.PublicKey)
encodedContact, err := json.Marshal(contact)
require.NoError(t, err)
require.True(t, strings.Contains(string(encodedContact), "compressedKey\":\"zQ"))
require.True(t, strings.Contains(string(encodedContact), "mutual\":true"))
require.True(t, strings.Contains(string(encodedContact), "added\":true"))
require.True(t, strings.Contains(string(encodedContact), "hasAddedUs\":true"))
require.True(t, strings.Contains(string(encodedContact), "active\":true"))
require.True(t, strings.Contains(string(encodedContact), "primaryName\":\"primary-name"))
require.True(t, strings.Contains(string(encodedContact), "secondaryName\":\"secondary-name"))
require.True(t, strings.Contains(string(encodedContact), "emojiHash"))
}
func TestContactContactRequestPropagatedStateReceivedOutOfDateLocalStateOnTheirSide(t *testing.T) {
// We receive a message with expected contact request state != our state
// and clock < our clock, we ping back the user to reach consistency
c := &Contact{}
c.ContactRequestLocalState = ContactRequestStateSent
c.ContactRequestLocalClock = 1
result := c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateNone),
RemoteClock: 0,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 1,
},
)
require.True(t, result.sendBackState)
// if the state is the same, it should not send back a message
c = &Contact{}
c.ContactRequestLocalState = ContactRequestStateNone
c.ContactRequestLocalClock = 1
result = c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateNone),
RemoteClock: 0,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 1,
},
)
require.False(t, result.sendBackState)
// If the clock is the same, it should not send back a message
c = &Contact{}
c.ContactRequestLocalState = ContactRequestStateSent
c.ContactRequestLocalClock = 1
result = c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateNone),
RemoteClock: 1,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 1,
},
)
require.False(t, result.sendBackState)
}
func TestContactContactRequestPropagatedStateReceivedOutOfDateLocalStateOnOurSide(t *testing.T) {
// We receive a message with expected contact request state == none
// and clock > our clock. We consider this a retraction, unless we are
// in the dismissed state, since that should be only changed by a
// trusted device
c := &Contact{}
c.ContactRequestLocalState = ContactRequestStateSent
c.ContactRequestLocalClock = 1
c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateNone),
RemoteClock: 2,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 1,
},
)
require.False(t, c.added())
// But if it's dismissed, we don't change it
c = &Contact{}
c.ContactRequestLocalState = ContactRequestStateDismissed
c.ContactRequestLocalClock = 1
c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateNone),
RemoteClock: 1,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 2,
},
)
require.False(t, c.added())
require.True(t, c.dismissed())
// or if it's lower clock
c = &Contact{}
c.ContactRequestLocalState = ContactRequestStateSent
c.ContactRequestLocalClock = 1
c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateNone),
RemoteClock: 1,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 0,
},
)
require.True(t, c.added())
}
func TestContactContactRequestPropagatedStateReceivedOutOfDateRemoteState(t *testing.T) {
// We receive a message with newer remote state, we process it as we would for a normal contact request
c := &Contact{}
c.ContactRequestLocalState = ContactRequestStateSent
c.ContactRequestLocalClock = 1
c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateSent),
RemoteClock: 1,
LocalState: uint64(ContactRequestStateSent),
LocalClock: 1,
},
)
require.True(t, c.added())
require.True(t, c.mutual())
// and retraction
c = &Contact{}
c.ContactRequestLocalState = ContactRequestStateSent
c.ContactRequestLocalClock = 1
c.ContactRequestRemoteState = ContactRequestStateReceived
c.ContactRequestRemoteClock = 1
c.ContactRequestPropagatedStateReceived(
&protobuf.ContactRequestPropagatedState{
RemoteState: uint64(ContactRequestStateSent),
RemoteClock: 1,
LocalState: uint64(ContactRequestStateNone),
LocalClock: 2,
},
)
require.False(t, c.added())
require.False(t, c.hasAddedUs())
require.False(t, c.mutual())
}
func TestPrimaryName(t *testing.T) {
// Has only Alias
contact := &Contact{
Alias: "alias",
}
require.Equal(t, "alias", contact.PrimaryName())
// Has display name
contact.DisplayName = "display-name"
require.Equal(t, "display-name", contact.PrimaryName())
require.Equal(t, "", contact.SecondaryName())
// Has non verified ens name
contact.EnsName = "ens-name"
require.Equal(t, "display-name", contact.PrimaryName())
require.Equal(t, "", contact.SecondaryName())
// Has verified ens name
contact.ENSVerified = true
require.Equal(t, "ens-name", contact.PrimaryName())
require.Equal(t, "", contact.SecondaryName())
contact.LocalNickname = "nickname"
// Has nickname and ENS name
require.Equal(t, "nickname", contact.PrimaryName())
require.Equal(t, "ens-name", contact.SecondaryName())
// Has nickname and display name
contact.EnsName = ""
require.Equal(t, "nickname", contact.PrimaryName())
require.Equal(t, "display-name", contact.SecondaryName())
// Has nickname and alias
contact.DisplayName = ""
require.Equal(t, "nickname", contact.PrimaryName())
require.Equal(t, "alias", contact.SecondaryName())
}
func TestProcessSyncContactRequestState(t *testing.T) {
c := &Contact{}
c.ContactRequestLocalState = ContactRequestStateNone
c.ContactRequestLocalClock = 1
c.ContactRequestRemoteState = ContactRequestStateNone
c.ContactRequestRemoteClock = 1
c.ProcessSyncContactRequestState(ContactRequestStateNone, 2, ContactRequestStateSent, 2)
// Here we need to confirm that resulting Local/RemoteState is equal
// to what comes from the contact sync message, otherwise it will be inconsistent
require.Equal(t, ContactRequestStateSent, c.ContactRequestLocalState)
require.Equal(t, ContactRequestStateNone, c.ContactRequestRemoteState)
}