github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/contacts/contacts_test.go (about)

     1  // Copyright 2019 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package contacts
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/keybase/client/go/externals"
    12  	"github.com/keybase/client/go/libkb"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func stringifyResults(res []keybase1.ProcessedContact) (ret []string) {
    18  	ret = make([]string, len(res))
    19  	for i, r := range res {
    20  		if r.Resolved {
    21  			ret[i] = fmt.Sprintf("keybase:%s", r.Username)
    22  		} else {
    23  			var phoneOrEmail string
    24  			if r.Component.PhoneNumber != nil {
    25  				phoneOrEmail = string(*r.Component.PhoneNumber)
    26  			} else if r.Component.Email != nil {
    27  				phoneOrEmail = string(*r.Component.Email)
    28  			}
    29  			ret[i] = fmt.Sprintf("%s %s (%s)", r.DisplayName, phoneOrEmail, r.Component.Label)
    30  		}
    31  	}
    32  	return ret
    33  }
    34  
    35  func displayResults(res []keybase1.ProcessedContact) (ret []string) {
    36  	ret = make([]string, len(res))
    37  	for i, v := range res {
    38  		ret[i] = fmt.Sprintf("%q %q", v.DisplayName, v.DisplayLabel)
    39  	}
    40  	return ret
    41  }
    42  
    43  func TestLookupEmptyList(t *testing.T) {
    44  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
    45  	defer tc.Cleanup()
    46  
    47  	provider := &ErrorContactsProvider{T: t}
    48  	contactList := []keybase1.Contact{}
    49  
    50  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
    51  	require.NoError(t, err)
    52  	require.Len(t, res, 0)
    53  }
    54  
    55  func TestLookupContacts(t *testing.T) {
    56  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
    57  	defer tc.Cleanup()
    58  
    59  	contactList := []keybase1.Contact{
    60  		{
    61  			Name: "Joe",
    62  			Components: []keybase1.ContactComponent{
    63  				MakePhoneComponent("Home", "+1111222"),
    64  				MakePhoneComponent("Work", "+199123"),
    65  				MakeEmailComponent("E-mail", "joe@linux.org"),
    66  			},
    67  		},
    68  	}
    69  
    70  	provider := MakeMockProvider(t)
    71  
    72  	actx := externals.MakeStaticAssertionContext(context.Background())
    73  
    74  	// None of the contact components resolved (empty mock provider). Return all
    75  	// 3 unresolved components to the caller.
    76  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
    77  	require.NoError(t, err)
    78  	require.Len(t, res, 3)
    79  	for i, r := range res {
    80  		require.Equal(t, "Joe", r.DisplayName)
    81  		require.False(t, r.Resolved)
    82  		require.True(t, r.Uid.IsNil())
    83  		component := contactList[0].Components[i]
    84  		assertion, err := AssertionFromComponent(actx, component, "")
    85  		require.NoError(t, err)
    86  		require.Equal(t, assertion, r.Assertion)
    87  	}
    88  
    89  	// Phone number component will not resolve. Still save all 3 components.
    90  	mockJoe := MakeMockLookupUser("joe", "JOE")
    91  	provider.PhoneNumbers["+1111222"] = mockJoe
    92  
    93  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
    94  	require.NoError(t, err)
    95  	require.Len(t, res, 3)
    96  	require.True(t, res[0].Resolved)
    97  	require.NotNil(t, res[0].Component.PhoneNumber)
    98  	require.Equal(t, "1111222@phone", res[0].Assertion)
    99  	for i, v := range res {
   100  		if i != 0 {
   101  			require.False(t, v.Resolved)
   102  			require.Equal(t, "Joe", v.DisplayName)
   103  			assertion, err := AssertionFromComponent(actx, v.Component, "")
   104  			require.NoError(t, err)
   105  			require.Equal(t, assertion, v.Assertion)
   106  		}
   107  	}
   108  
   109  	// Second number also belongs to joe now. Still save all entries.
   110  	provider.PhoneNumbers["+199123"] = mockJoe
   111  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   112  	require.NoError(t, err)
   113  	require.Len(t, res, 3)
   114  	for i, v := range res {
   115  		if i == 2 {
   116  			require.False(t, v.Resolved)
   117  			require.Equal(t, "Joe", v.DisplayName)
   118  			assertion, err := AssertionFromComponent(actx, v.Component, "")
   119  			require.NoError(t, err)
   120  			require.Equal(t, assertion, v.Assertion)
   121  		}
   122  	}
   123  
   124  	// Suddenly this number resolves to someone else, despite being in same contact.
   125  	provider.PhoneNumbers["+199123"] = MakeMockLookupUser("ed", "")
   126  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   127  	require.NoError(t, err)
   128  	require.Len(t, res, 3)
   129  	require.Equal(t, []string{
   130  		"keybase:joe",
   131  		"keybase:ed",
   132  		"Joe joe@linux.org (E-mail)",
   133  	}, stringifyResults(res))
   134  	// Even if contact resolves to a Keybase user, assertion should still be an
   135  	// imptofu assertion (rather than username or username@keybase). This is
   136  	// required, so resolver is involved when starting a conversation.
   137  	require.Equal(t, "1111222@phone", res[0].Assertion)
   138  	require.Equal(t, "199123@phone", res[1].Assertion)
   139  	require.Equal(t, "[joe@linux.org]@email", res[2].Assertion)
   140  
   141  	// Test with email
   142  	provider = MakeMockProvider(t) // *new provider*
   143  	provider.Emails["joe@linux.org"] = mockJoe
   144  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   145  	require.NoError(t, err)
   146  	require.Len(t, res, 3)
   147  	// Only last component (the one with email) resolves, rest is unresolved.
   148  	for i, v := range res {
   149  		if i != 2 {
   150  			require.False(t, v.Resolved)
   151  			require.Equal(t, "Joe", v.DisplayName)
   152  			assertion, err := AssertionFromComponent(actx, v.Component, "")
   153  			require.NoError(t, err)
   154  			require.Equal(t, assertion, v.Assertion)
   155  		}
   156  	}
   157  	require.Equal(t, "joe", res[2].DisplayName)
   158  	require.Equal(t, "E-mail", res[2].Component.Label)
   159  	require.Nil(t, res[2].Component.PhoneNumber)
   160  	require.NotNil(t, res[2].Component.Email)
   161  	require.EqualValues(t, "joe@linux.org", *res[2].Component.Email)
   162  	require.True(t, res[2].Resolved)
   163  	require.Equal(t, mockJoe.UID, res[2].Uid)
   164  }
   165  
   166  func TestLookupContactsMultipleUsers(t *testing.T) {
   167  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
   168  	defer tc.Cleanup()
   169  
   170  	contactList := []keybase1.Contact{
   171  		{
   172  			Name: "Alice",
   173  			Components: []keybase1.ContactComponent{
   174  				MakePhoneComponent("Home", "+1111222"),
   175  				MakePhoneComponent("Work", "+199123"),
   176  			},
   177  		},
   178  		{
   179  			Name: "Bob",
   180  			Components: []keybase1.ContactComponent{
   181  				MakePhoneComponent("Home", "+123456"),
   182  			},
   183  		},
   184  		{
   185  			Name: "Charlie",
   186  			Components: []keybase1.ContactComponent{
   187  				MakeEmailComponent("E-mail", "charlie+test@keyba.se"),
   188  			},
   189  		},
   190  	}
   191  
   192  	provider := MakeMockProvider(t)
   193  
   194  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   195  	require.NoError(t, err)
   196  	expected := []string{
   197  		"Alice +1111222 (Home)",
   198  		"Alice +199123 (Work)",
   199  		"Bob +123456 (Home)",
   200  		"Charlie charlie+test@keyba.se (E-mail)",
   201  	}
   202  	require.Equal(t, expected, stringifyResults(res))
   203  
   204  	provider.PhoneNumbers["+123456"] = MakeMockLookupUser("bob", "")
   205  	provider.Emails["charlie+test@keyba.se"] = MakeMockLookupUser("charlie", "")
   206  
   207  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   208  	require.NoError(t, err)
   209  	expected = []string{
   210  		"Alice +1111222 (Home)",
   211  		"Alice +199123 (Work)",
   212  		"keybase:bob",
   213  		"keybase:charlie",
   214  	}
   215  	require.Equal(t, expected, stringifyResults(res))
   216  	expected = []string{
   217  		`"Alice" "+1111222 (Home)"`,
   218  		`"Alice" "+199123 (Work)"`,
   219  		`"bob" "Bob"`,
   220  		`"charlie" "Charlie"`,
   221  	}
   222  	require.Equal(t, expected, displayResults(res))
   223  
   224  	require.Equal(t, "1111222@phone", res[0].Assertion)
   225  	require.Equal(t, "199123@phone", res[1].Assertion)
   226  	require.Equal(t, "123456@phone", res[2].Assertion)
   227  	require.Equal(t, "[charlie+test@keyba.se]@email", res[3].Assertion)
   228  }
   229  
   230  func TestEmptyComponentLabels(t *testing.T) {
   231  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
   232  	defer tc.Cleanup()
   233  
   234  	contactList := []keybase1.Contact{
   235  		{
   236  			Name: "Alice",
   237  			Components: []keybase1.ContactComponent{
   238  				MakePhoneComponent("", "+1111222"),
   239  				MakeEmailComponent("", "alice+test@keyba.se"),
   240  			},
   241  		},
   242  	}
   243  
   244  	provider := MakeMockProvider(t)
   245  
   246  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   247  	require.NoError(t, err)
   248  	expected := []string{
   249  		"Alice +1111222 ()",
   250  		"Alice alice+test@keyba.se ()",
   251  	}
   252  	require.Equal(t, expected, stringifyResults(res))
   253  	expected = []string{
   254  		`"Alice" "+1111222"`,
   255  		`"Alice" "alice+test@keyba.se"`,
   256  	}
   257  	require.Equal(t, expected, displayResults(res))
   258  
   259  	provider.Emails["alice+test@keyba.se"] = MockLookupUser{UID: keybase1.UID("1111"), Username: "alice", Fullname: "A L I C E"}
   260  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   261  	require.NoError(t, err)
   262  	require.Len(t, res, 2)
   263  	// First component did not resolve
   264  	require.False(t, res[0].Resolved)
   265  	require.Equal(t, "1111222@phone", res[0].Assertion)
   266  	// Second component resolved
   267  	require.True(t, res[1].Resolved)
   268  	require.Equal(t, "alice", res[1].Username)
   269  	require.Equal(t, "A L I C E", res[1].FullName)
   270  	require.EqualValues(t, "1111", res[1].Uid)
   271  	require.False(t, res[1].Following)
   272  	require.Equal(t, "alice", res[1].DisplayName)
   273  	require.Equal(t, "Alice", res[1].DisplayLabel) // Because we are not following, contact name is used instead of full name.
   274  	require.Equal(t, "[alice+test@keyba.se]@email", res[1].Assertion)
   275  }
   276  
   277  func TestFollowing(t *testing.T) {
   278  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
   279  	defer tc.Cleanup()
   280  
   281  	contactList := []keybase1.Contact{
   282  		{
   283  			Name: "Alice",
   284  			Components: []keybase1.ContactComponent{
   285  				MakePhoneComponent("", "+1111222"),
   286  			},
   287  		},
   288  		{
   289  			Name: "Bob",
   290  			Components: []keybase1.ContactComponent{
   291  				MakeEmailComponent("", "bob+test@keyba.se"),
   292  			},
   293  		},
   294  		{
   295  			Name: "Charlie",
   296  			Components: []keybase1.ContactComponent{
   297  				MakeEmailComponent("", "charlie+test@keyba.se"),
   298  			},
   299  		},
   300  		{
   301  			Name: "",
   302  			Components: []keybase1.ContactComponent{
   303  				MakeEmailComponent("", "doug+test@keyba.se"),
   304  			},
   305  		},
   306  	}
   307  
   308  	provider := MakeMockProvider(t)
   309  
   310  	provider.PhoneNumbers["+1111222"] = MockLookupUser{UID: keybase1.UID("1111"), Username: "alice", Fullname: "CryptoAlice"}
   311  	provider.Emails["bob+test@keyba.se"] = MockLookupUser{UID: keybase1.UID("2222"), Username: "bob", Fullname: ""}
   312  	provider.Emails["charlie+test@keyba.se"] = MockLookupUser{UID: keybase1.UID("3333"), Username: "charlie", Fullname: ""}
   313  	provider.Emails["doug+test@keyba.se"] = MockLookupUser{UID: keybase1.UID("4444"), Username: "doug", Fullname: ""}
   314  
   315  	provider.Following[keybase1.UID("1111")] = true
   316  	provider.Following[keybase1.UID("3333")] = true
   317  
   318  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   319  	require.NoError(t, err)
   320  	require.Len(t, res, 4)
   321  	expected := []string{
   322  		`"alice" "CryptoAlice"`,       // followed and have full name, take full name
   323  		`"bob" "Bob"`,                 // not followed, no full name, take contact name
   324  		`"charlie" "Charlie"`,         // followed but no full name, take contact name
   325  		`"doug" "doug+test@keyba.se"`, // not followed, no full name, no contact name, take component
   326  	}
   327  	require.Equal(t, expected, displayResults(res))
   328  }
   329  
   330  func TestErrorsInResolution(t *testing.T) {
   331  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
   332  	defer tc.Cleanup()
   333  
   334  	contactList := []keybase1.Contact{
   335  		{
   336  			Name: "Alice",
   337  			Components: []keybase1.ContactComponent{
   338  				MakePhoneComponent("home", "+1111222"),
   339  				MakePhoneComponent("work", "444"),
   340  			},
   341  		},
   342  		{
   343  			Name: "Bob",
   344  			Components: []keybase1.ContactComponent{
   345  				MakeEmailComponent("", "bob+test@keyba.se"),
   346  			},
   347  		},
   348  	}
   349  
   350  	provider := MakeMockProvider(t)
   351  	provider.PhoneNumbers["+1111222"] = MakeMockLookupUser("alice", "CryptoAlice")
   352  	provider.PhoneNumberErrors["444"] = "Mock error for number 444"
   353  
   354  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   355  	require.NoError(t, err)
   356  	require.Len(t, res, 2)
   357  	require.True(t, res[0].Resolved)
   358  	require.False(t, res[1].Resolved)
   359  	expected := []string{
   360  		`"alice" "Alice"`,
   361  		`"Bob" "bob+test@keyba.se"`,
   362  	}
   363  	require.Equal(t, expected, displayResults(res))
   364  
   365  	delete(provider.PhoneNumbers, "+1111222")
   366  
   367  	res, err = ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   368  	require.NoError(t, err)
   369  	require.Len(t, res, 2)
   370  	require.False(t, res[0].Resolved)
   371  	require.False(t, res[1].Resolved)
   372  	expected = []string{
   373  		`"Alice" "+1111222 (home)"`,
   374  		`"Bob" "bob+test@keyba.se"`,
   375  	}
   376  	require.Equal(t, expected, displayResults(res))
   377  }
   378  
   379  func TestDuplicateEntries(t *testing.T) {
   380  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
   381  	defer tc.Cleanup()
   382  
   383  	contactList := []keybase1.Contact{
   384  		// Contact with multiple components that will yield the same assertion.
   385  		{
   386  			Name: "Alice",
   387  			Components: []keybase1.ContactComponent{
   388  				MakePhoneComponent("home", "+1111222"),
   389  				MakePhoneComponent("car", "+1111222"),
   390  				MakePhoneComponent("car", "+1111222"),
   391  			},
   392  		},
   393  		// "Duplicated" contacts with the same name and same component.
   394  		{
   395  			Name: "Alice",
   396  			Components: []keybase1.ContactComponent{
   397  				MakePhoneComponent("home", "+1111222"),
   398  			},
   399  		},
   400  		// Two contacts with same component that's going to get resolved -
   401  		// resolution should appear only once in results.
   402  		{
   403  			Name: "Bob",
   404  			Components: []keybase1.ContactComponent{
   405  				MakeEmailComponent("email", "bob+test@keyba.se"),
   406  			},
   407  		},
   408  		{
   409  			Name: "Robert B.",
   410  			Components: []keybase1.ContactComponent{
   411  				MakeEmailComponent("E-Mail", "bob+test@keyba.se"),
   412  			},
   413  		},
   414  	}
   415  
   416  	provider := MakeMockProvider(t)
   417  	provider.Emails["bob+test@keyba.se"] = MakeMockLookupUser("bob", "Bobby")
   418  
   419  	// We expect one entry for Alice, +1111222 - because there is only one
   420  	// unique combination of "Alice" contact name ane component value
   421  	// "+1111222". Both "bob+test@keyba.se" values for Bob/Robert should have
   422  	// own, resolved, entry.
   423  	res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), provider, contactList)
   424  	require.NoError(t, err)
   425  	require.Len(t, res, 3)
   426  	expected := []string{
   427  		`"Alice" "+1111222 (home)"`,
   428  		`"bob" "Bob"`,
   429  		`"bob" "Robert B."`,
   430  	}
   431  	require.Equal(t, expected, displayResults(res))
   432  	require.False(t, res[0].Resolved)
   433  	require.True(t, res[1].Resolved)
   434  	require.True(t, res[2].Resolved)
   435  }