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

     1  package service
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/keybase/client/go/contacts"
     8  	"github.com/keybase/client/go/kbtest"
     9  	"github.com/keybase/client/go/libkb"
    10  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    11  	"github.com/keybase/clockwork"
    12  	"github.com/stretchr/testify/require"
    13  	"golang.org/x/net/context"
    14  )
    15  
    16  // Test "end-to-end" syncing contact (by using SaveContactList RPC with raw
    17  // contact list) and interacting with it using UserSearch RPCs.
    18  
    19  // Mock contact provider.
    20  
    21  type contactSyncTest struct {
    22  	searchHandler   *UserSearchHandler
    23  	contactsHandler *ContactsHandler
    24  	contactsMock    *contacts.MockContactsProvider
    25  	searchMock      *testUserSearchProvider
    26  	user            *kbtest.FakeUser
    27  
    28  	contactsCache *contacts.CachedContactsProvider
    29  }
    30  
    31  func setupContactSyncTest(t *testing.T) (tc libkb.TestContext, test contactSyncTest) {
    32  	tc = libkb.SetupTest(t, "contacts", 3)
    33  	tc.G.SyncedContactList = contacts.NewSavedContactsStore(tc.G)
    34  
    35  	user, err := kbtest.CreateAndSignupFakeUser("lmu", tc.G)
    36  	require.NoError(t, err)
    37  
    38  	mockContactsProv := contacts.MakeMockProvider(t)
    39  	contactsProv := &contacts.CachedContactsProvider{
    40  		Provider: mockContactsProv,
    41  		Store:    contacts.NewContactCacheStore(tc.G),
    42  	}
    43  	searchHandler := NewUserSearchHandler(nil, tc.G, contactsProv)
    44  	searchProv := &testUserSearchProvider{T: t}
    45  	searchHandler.searchProvider = searchProv
    46  
    47  	contactsHandler := NewContactsHandler(nil, tc.G, contactsProv)
    48  	return tc, contactSyncTest{
    49  		searchHandler:   searchHandler,
    50  		contactsHandler: contactsHandler,
    51  		contactsMock:    mockContactsProv,
    52  		searchMock:      searchProv,
    53  		user:            user,
    54  		contactsCache:   contactsProv,
    55  	}
    56  }
    57  
    58  func (c *contactSyncTest) clearCache(mctx libkb.MetaContext) error {
    59  	return c.contactsCache.Store.ClearCache(mctx)
    60  }
    61  
    62  func TestContactSyncAndSearch(t *testing.T) {
    63  	tc, all := setupContactSyncTest(t)
    64  	defer tc.Cleanup()
    65  
    66  	clock := clockwork.NewFakeClock()
    67  	tc.G.SetClock(clock)
    68  
    69  	all.searchMock.addUser(testAddUserArg{username: "alice2"})
    70  
    71  	rawContacts := []keybase1.Contact{
    72  		contacts.MakeContact("Alice A",
    73  			contacts.MakePhoneComponent("mobile", "+48111222332"),
    74  		),
    75  		contacts.MakeContact("Alice A",
    76  			contacts.MakePhoneComponent("mobile", "+48111222333"),
    77  			contacts.MakeEmailComponent("email", "alice@example.org"),
    78  		),
    79  	}
    80  
    81  	// Phone component from rawContacts will resolve to user `alice` but e-mail
    82  	// will not. We are expecting to be able to search for both, but not see
    83  	// both in user recommendations.
    84  
    85  	_, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
    86  		Contacts: rawContacts,
    87  	})
    88  	require.NoError(t, err)
    89  
    90  	// bust cache, new resolution should be returned
    91  	clock.Advance(72 * time.Hour)
    92  	all.contactsMock.PhoneNumbers["+48111222332"] = contacts.MakeMockLookupUser("alice", "")
    93  	all.contactsMock.PhoneNumbers["+48111222333"] = contacts.MakeMockLookupUser("alice", "")
    94  	result, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
    95  		Contacts: rawContacts,
    96  	})
    97  	require.NoError(t, err)
    98  
    99  	newlyResolved := result.NewlyResolved
   100  	// We should only have 1 resolved, since we dedupe.
   101  	require.Len(t, newlyResolved, 1)
   102  	require.Equal(t, newlyResolved[0].ContactName, "Alice A")
   103  	require.Equal(t, newlyResolved[0].Username, "alice")
   104  	require.Equal(t, newlyResolved[0].Assertion, "48111222333@phone")
   105  
   106  	{
   107  		// Try raw contact list lookup.
   108  		list, err := all.contactsHandler.LookupSavedContactsList(context.Background(), 0)
   109  		require.NoError(t, err)
   110  		// We have two contacts with three components between them
   111  		require.Len(t, list, 3)
   112  	}
   113  
   114  	{
   115  		// We expect one "Alice A" show up as resolved. She should be the second one.
   116  		list, err := all.contactsHandler.GetContactsForUserRecommendations(context.Background(), 0)
   117  		require.NoError(t, err)
   118  		require.Len(t, list, 1)
   119  		require.Equal(t, "alice", list[0].DisplayName)
   120  		require.Equal(t, "Alice A", list[0].DisplayLabel)
   121  		require.Equal(t, "alice", list[0].Username)
   122  		require.NotNil(t, list[0].Component.PhoneNumber)
   123  		require.Equal(t, "48111222332@phone", list[0].Assertion)
   124  	}
   125  
   126  	{
   127  		// When searching for "alice" in contacts, contact comes first because
   128  		// it's a better match than `alice2` user.
   129  
   130  		// NOTE: This test is very unrealistic because contact resolves to user
   131  		// `alice` but that user is not provided by mocked search provider. If
   132  		// it was, Keybase result would have precedence, and the first result
   133  		// would not be SBS contact result.
   134  		res, err := all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   135  			IncludeContacts: true,
   136  			Service:         "keybase",
   137  			Query:           "alice",
   138  			MaxResults:      50,
   139  		})
   140  		require.NoError(t, err)
   141  		require.Len(t, res, 2)
   142  		pres := pluckAllSearchResultForTest(res)
   143  		require.Equal(t, "48111222332@phone", pres[0].id)
   144  		require.Equal(t, "alice", pres[0].keybaseUsername)
   145  		require.Equal(t, "alice2", pres[1].id)
   146  		require.Equal(t, "alice2", pres[1].keybaseUsername)
   147  	}
   148  
   149  	{
   150  		// Should be possible to search for both alice's components - as long a
   151  		// search don't yield both at the same time, then it's handled like in
   152  		// the previous case.
   153  		res, err := all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   154  			IncludeContacts: true,
   155  			Service:         "keybase",
   156  			Query:           "111222333",
   157  			MaxResults:      50,
   158  		})
   159  		require.NoError(t, err)
   160  		require.Len(t, res, 1)
   161  		require.NotNil(t, res[0].Contact)
   162  		pres := pluckSearchResultForTest(res[0])
   163  		require.Equal(t, "48111222333@phone", pres.id)
   164  		require.Equal(t, "alice", pres.displayName)
   165  		require.Equal(t, "+48111222333", pres.displayLabel)
   166  
   167  		res, err = all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   168  			IncludeContacts: true,
   169  			Service:         "keybase",
   170  			Query:           "alice@example.org",
   171  			MaxResults:      50,
   172  		})
   173  		require.NoError(t, err)
   174  		require.Len(t, res, 1)
   175  		require.NotNil(t, res[0].Contact)
   176  		pres = pluckSearchResultForTest(res[0])
   177  		require.Equal(t, "[alice@example.org]@email", pres.id)
   178  		require.Equal(t, "Alice A", pres.displayName)
   179  		require.Equal(t, "alice@example.org (email)", pres.displayLabel)
   180  	}
   181  }
   182  
   183  func TestContactShouldFilterOutSelf(t *testing.T) {
   184  	tc, all := setupContactSyncTest(t)
   185  	defer tc.Cleanup()
   186  
   187  	all.searchMock.addUser(testAddUserArg{username: "alice2"})
   188  
   189  	all.contactsMock.PhoneNumbers["+1555222"] = contacts.MakeMockLookupUser(all.user.Username, "")
   190  
   191  	rawContacts := []keybase1.Contact{
   192  		contacts.MakeContact("Alice A",
   193  			contacts.MakePhoneComponent("mobile", "+48111222333"),
   194  			contacts.MakeEmailComponent("email", "alice@example.org"),
   195  		),
   196  		contacts.MakeContact("Charlie",
   197  			contacts.MakePhoneComponent("mobile", "+1555222"),
   198  		),
   199  	}
   200  
   201  	_, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
   202  		Contacts: rawContacts,
   203  	})
   204  	require.NoError(t, err)
   205  
   206  	list, err := all.contactsHandler.GetContactsForUserRecommendations(context.Background(), 0)
   207  	require.NoError(t, err)
   208  	require.Len(t, list, 2)
   209  	for _, v := range list {
   210  		// Only first contact entries should be there
   211  		require.Equal(t, "Alice A", v.ContactName)
   212  		require.Equal(t, 0, v.ContactIndex)
   213  	}
   214  }
   215  
   216  func TestRecommendationsPreferEmail(t *testing.T) {
   217  	tc, all := setupContactSyncTest(t)
   218  	defer tc.Cleanup()
   219  
   220  	all.contactsMock.PhoneNumbers["+48111222333"] = contacts.MakeMockLookupUser("alice", "")
   221  	all.contactsMock.Emails["alice@example.org"] = contacts.MakeMockLookupUser("alice", "")
   222  
   223  	rawContacts := []keybase1.Contact{
   224  		contacts.MakeContact("Alice A",
   225  			contacts.MakePhoneComponent("mobile", "+48111222333"),
   226  			contacts.MakeEmailComponent("email", "alice@example.org"),
   227  		),
   228  	}
   229  
   230  	_, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
   231  		Contacts: rawContacts,
   232  	})
   233  	require.NoError(t, err)
   234  
   235  	list, err := all.contactsHandler.GetContactsForUserRecommendations(context.Background(), 0)
   236  	require.NoError(t, err)
   237  	require.Len(t, list, 1)
   238  	require.True(t, list[0].Resolved)
   239  	require.Equal(t, "alice", list[0].Username)
   240  	require.Equal(t, "[alice@example.org]@email", list[0].Assertion)
   241  }
   242  
   243  func TestDuplicateContactAssertions(t *testing.T) {
   244  	tc, all := setupContactSyncTest(t)
   245  	defer tc.Cleanup()
   246  
   247  	rawContacts := []keybase1.Contact{
   248  		contacts.MakeContact("Alice A",
   249  			contacts.MakePhoneComponent("mobile", "+48111222333"),
   250  		),
   251  		contacts.MakeContact("Mom",
   252  			contacts.MakePhoneComponent("mobile", "+48111222333"),
   253  		),
   254  	}
   255  
   256  	_, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
   257  		Contacts: rawContacts,
   258  	})
   259  	require.NoError(t, err)
   260  
   261  	{
   262  		// We expect to see both contacts here.
   263  		res, err := all.contactsHandler.GetContactsForUserRecommendations(context.Background(), 0)
   264  		require.NoError(t, err)
   265  		require.Len(t, res, 2)
   266  	}
   267  
   268  	{
   269  		// Same when searching
   270  		res, err := all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   271  			IncludeContacts: true,
   272  			Service:         "keybase",
   273  			Query:           "111",
   274  			MaxResults:      50,
   275  		})
   276  		require.NoError(t, err)
   277  		require.Len(t, res, 2)
   278  	}
   279  
   280  	{
   281  		// Make the number resolvable, re-import contacts.
   282  		all.contactsMock.PhoneNumbers["+48111222333"] = contacts.MakeMockLookupUser("alice", "A. Alice")
   283  
   284  		require.NoError(t, all.clearCache(tc.MetaContext()))
   285  		_, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
   286  			Contacts: rawContacts,
   287  		})
   288  		require.NoError(t, err)
   289  	}
   290  
   291  	{
   292  		// We expect to see only one result here. Second contact is filtered out
   293  		// because of username deduplication.
   294  		res, err := all.contactsHandler.GetContactsForUserRecommendations(context.Background(), 0)
   295  		require.NoError(t, err)
   296  		require.Len(t, res, 1)
   297  		require.Equal(t, "alice", res[0].DisplayName)
   298  		require.Equal(t, "Alice A", res[0].DisplayLabel)
   299  		require.Equal(t, "48111222333@phone", res[0].Assertion)
   300  	}
   301  
   302  	{
   303  		// Only one contact when searching as well
   304  		res, err := all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   305  			IncludeContacts: true,
   306  			Service:         "keybase",
   307  			Query:           "111",
   308  			MaxResults:      50,
   309  		})
   310  		require.NoError(t, err)
   311  		require.Len(t, res, 1)
   312  		pres := pluckSearchResultForTest(res[0])
   313  		require.Equal(t, "48111222333@phone", pres.id)
   314  		require.Equal(t, "alice", pres.displayName)
   315  		// Selecting query match to display label is still at play here.
   316  		require.Equal(t, "+48111222333", pres.displayLabel)
   317  	}
   318  }
   319  
   320  func TestSyncContactsWithServiceSummary(t *testing.T) {
   321  	tc, all := setupContactSyncTest(t)
   322  	defer tc.Cleanup()
   323  
   324  	aliceMock := contacts.MakeMockLookupUser("alice", "Alice Keybase")
   325  	aliceMock.ServiceMap = make(libkb.UserServiceSummary)
   326  	aliceMock.ServiceMap["rooter"] = "alice123"
   327  	aliceMock.ServiceMap["twitter"] = "tacovontaco"
   328  
   329  	all.contactsMock.Emails["alice@keyba.se"] = aliceMock
   330  	all.contactsMock.Emails["bob@keyba.se"] = contacts.MakeMockLookupUser("bob", "Bob Keybase")
   331  
   332  	rawContacts := []keybase1.Contact{
   333  		contacts.MakeContact("Alice",
   334  			contacts.MakeEmailComponent("email", "alice@keyba.se"),
   335  		),
   336  		contacts.MakeContact("Bob",
   337  			contacts.MakeEmailComponent("email", "bob@keyba.se"),
   338  		),
   339  	}
   340  
   341  	_, err := all.contactsHandler.SaveContactList(context.Background(), keybase1.SaveContactListArg{
   342  		Contacts: rawContacts,
   343  	})
   344  	require.NoError(t, err)
   345  
   346  	res, err := all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   347  		IncludeContacts: true,
   348  		Service:         "keybase",
   349  		Query:           "keyba.se",
   350  		MaxResults:      50,
   351  	})
   352  	require.NoError(t, err)
   353  	require.Len(t, res, 2)
   354  	// Bob (no service map because provider returned no services)
   355  	require.NotNil(t, res[0].Contact)
   356  	require.Equal(t, "Bob", res[0].Contact.ContactName)
   357  	require.Nil(t, res[0].Contact.ServiceMap)
   358  	// Alice (with service map)
   359  	require.NotNil(t, res[1].Contact)
   360  	require.Equal(t, "Alice", res[1].Contact.ContactName)
   361  	require.NotNil(t, res[1].Contact.ServiceMap)
   362  	require.Len(t, res[1].Contact.ServiceMap, 2)
   363  	require.Equal(t, "alice123", res[1].Contact.ServiceMap["rooter"])
   364  	require.Equal(t, "tacovontaco", res[1].Contact.ServiceMap["twitter"])
   365  }
   366  
   367  func TestUserSearchPhoneEmailWithResults(t *testing.T) {
   368  	tc, all := setupContactSyncTest(t)
   369  	defer tc.Cleanup()
   370  
   371  	all.contactsMock.Emails["fermatp@keyba.se"] = contacts.MakeMockLookupUser("pierre", "Pierre de Fermat")
   372  	all.contactsMock.PhoneNumbers["+1555165432"] = contacts.MakeMockLookupUser("lwg", "Gottfried Wilhelm Leibniz")
   373  
   374  	doSearch := func(service, query string) []keybase1.APIUserSearchResult {
   375  		res, err := all.searchHandler.UserSearch(context.Background(), keybase1.UserSearchArg{
   376  			IncludeContacts: false,
   377  			Service:         service,
   378  			Query:           query,
   379  			MaxResults:      10,
   380  		})
   381  		require.NoError(t, err)
   382  		return res
   383  	}
   384  
   385  	// Do imptofu queries such that they will resolve when looked up by
   386  	// contacts provider.
   387  
   388  	{
   389  		query := "+1555165432"
   390  		res := doSearch("phone", query)
   391  		require.Len(t, res, 1)
   392  		require.NotNil(t, res[0].Imptofu)
   393  		require.Equal(t, "1555165432@phone", res[0].Imptofu.Assertion)
   394  		require.Equal(t, "lwg", res[0].Imptofu.KeybaseUsername)
   395  		require.Equal(t, "Gottfried Wilhelm Leibniz", res[0].Imptofu.PrettyName)
   396  	}
   397  
   398  	{
   399  		query := "fermatp@keyba.se"
   400  		res := doSearch("email", query)
   401  		require.Len(t, res, 1)
   402  		require.NotNil(t, res[0].Imptofu)
   403  		require.Equal(t, "[fermatp@keyba.se]@email", res[0].Imptofu.Assertion)
   404  		require.Equal(t, "pierre", res[0].Imptofu.KeybaseUsername)
   405  		require.Equal(t, "Pierre de Fermat", res[0].Imptofu.PrettyName)
   406  	}
   407  }