github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/contacts/cache_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  	"testing"
     8  	"time"
     9  
    10  	"github.com/keybase/client/go/kbtest"
    11  	"github.com/keybase/clockwork"
    12  
    13  	"github.com/keybase/client/go/libkb"
    14  	"github.com/keybase/client/go/protocol/keybase1"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  type anotherMockContactsProvider struct {
    19  	provider   *MockContactsProvider
    20  	t          *testing.T
    21  	disabled   bool
    22  	queryCount int
    23  }
    24  
    25  func (c *anotherMockContactsProvider) LookupAllWithToken(mctx libkb.MetaContext, emails []keybase1.EmailAddress,
    26  	numbers []keybase1.RawPhoneNumber, _ Token) (ContactLookupResults, error) {
    27  	return c.LookupAll(mctx, emails, numbers)
    28  }
    29  
    30  func (c *anotherMockContactsProvider) LookupAll(mctx libkb.MetaContext, emails []keybase1.EmailAddress,
    31  	numbers []keybase1.RawPhoneNumber) (ContactLookupResults, error) {
    32  
    33  	if c.disabled {
    34  		require.FailNow(c.t, "unexpected call to provider, after being disabled")
    35  	}
    36  	c.queryCount += len(emails) + len(numbers)
    37  	return c.provider.LookupAll(mctx, emails, numbers)
    38  }
    39  
    40  func (c *anotherMockContactsProvider) FindUsernames(mctx libkb.MetaContext, uids []keybase1.UID) (map[keybase1.UID]ContactUsernameAndFullName, error) {
    41  	return c.provider.FindUsernames(mctx, uids)
    42  }
    43  
    44  func (c *anotherMockContactsProvider) FindFollowing(mctx libkb.MetaContext, uids []keybase1.UID) (map[keybase1.UID]bool, error) {
    45  	return c.provider.FindFollowing(mctx, uids)
    46  }
    47  
    48  func (c *anotherMockContactsProvider) FindServiceMaps(mctx libkb.MetaContext, uids []keybase1.UID) (map[keybase1.UID]libkb.UserServiceSummary, error) {
    49  	return c.provider.FindServiceMaps(mctx, uids)
    50  }
    51  
    52  func TestCacheProvider(t *testing.T) {
    53  	tc := libkb.SetupTest(t, "TestCacheProvider", 1)
    54  	defer tc.Cleanup()
    55  
    56  	mockProvider := MakeMockProvider(t)
    57  	cacheProvider := &CachedContactsProvider{
    58  		Provider: mockProvider,
    59  		Store:    NewContactCacheStore(tc.G),
    60  	}
    61  
    62  	res, err := cacheProvider.LookupAll(libkb.NewMetaContextForTest(tc), []keybase1.EmailAddress{}, []keybase1.RawPhoneNumber{})
    63  	require.NoError(t, err)
    64  	require.Len(t, res.Results, 0)
    65  }
    66  
    67  func setupTestCacheProviders(t *testing.T, tc libkb.TestContext) (provider *anotherMockContactsProvider,
    68  	cacheProvider *CachedContactsProvider) {
    69  
    70  	mockProvider := MakeMockProvider(t)
    71  	provider = &anotherMockContactsProvider{
    72  		provider: mockProvider,
    73  		t:        t,
    74  	}
    75  	cacheProvider = &CachedContactsProvider{
    76  		Provider: provider,
    77  		Store:    NewContactCacheStore(tc.G),
    78  	}
    79  
    80  	return provider, cacheProvider
    81  }
    82  
    83  func TestLookupCache(t *testing.T) {
    84  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
    85  	defer tc.Cleanup()
    86  
    87  	_, err := kbtest.CreateAndSignupFakeUser("tofu", tc.G)
    88  	require.NoError(t, err)
    89  
    90  	provider, cacheProvider := setupTestCacheProviders(t, tc)
    91  	mockProvider := provider.provider
    92  
    93  	// Test empty contact list
    94  	res0, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, []keybase1.Contact{})
    95  	require.NoError(t, err)
    96  	require.Len(t, res0, 0)
    97  
    98  	contactList := []keybase1.Contact{
    99  		{
   100  			Name: "Joe",
   101  			Components: []keybase1.ContactComponent{
   102  				MakePhoneComponent("Home", "+1111222"),
   103  				MakePhoneComponent("Work", "+199123"),
   104  				MakeEmailComponent("E-mail", "bob@keyba.se"),
   105  				MakeEmailComponent("E-mail 2", "b@keyba.se"),
   106  			},
   107  		},
   108  	}
   109  
   110  	mockProvider.PhoneNumbers["+1111222"] = MockLookupUser{UID: keybase1.UID("01ffffffffffffffffffffffffffff00"), Username: "bob"}
   111  	mockProvider.Emails["bob@keyba.se"] = MockLookupUser{UID: keybase1.UID("01ffffffffffffffffffffffffffff00"), Username: "bob"}
   112  	mockProvider.PhoneNumbers["+199123"] = MockLookupUser{UID: keybase1.UID("02ffffffffffffffffffffffffffff00"), Username: "other_bob"}
   113  
   114  	res1, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   115  	require.NoError(t, err)
   116  
   117  	// All components were processed.
   118  	require.Len(t, res1, 4)
   119  	// 4 calls to the provider, all components were queried
   120  	require.Equal(t, 4, provider.queryCount)
   121  
   122  	// Query again with the same contact list, we should not call cached
   123  	// provider's inner provider again. Everything should be obtained from
   124  	// cache, including components that did not yield a resolution during last
   125  	// call.
   126  	provider.disabled = true
   127  
   128  	res2, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   129  	require.NoError(t, err)
   130  	require.Equal(t, res1, res2)
   131  
   132  	// Add new component to the contact list, it will need to query again.
   133  	provider.disabled = false
   134  	provider.queryCount = 0
   135  
   136  	contactList[0].Components = append(contactList[0].Components, MakeEmailComponent("E-mail", "tester2@keyba.se"))
   137  
   138  	res2, err = ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   139  	require.NoError(t, err)
   140  	require.Len(t, res2, 5)
   141  	require.Equal(t, res1, res2[0:4])                               // first 4 elements are the same
   142  	require.Equal(t, "[tester2@keyba.se]@email", res2[4].Assertion) // new processed contact for new email
   143  	require.False(t, res2[4].Resolved)
   144  
   145  	require.Equal(t, 1, provider.queryCount) // only queried the new email
   146  
   147  	// Disable provider again.
   148  	provider.disabled = true
   149  	provider.queryCount = 0
   150  
   151  	res3, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   152  	require.NoError(t, err)
   153  	require.Equal(t, res2, res3)
   154  	require.Equal(t, 0, provider.queryCount) // new email got cached as well
   155  }
   156  
   157  func TestLookupCacheExpiration(t *testing.T) {
   158  	tc := libkb.SetupTest(t, "TestLookupContacts", 1)
   159  	defer tc.Cleanup()
   160  
   161  	_, err := kbtest.CreateAndSignupFakeUser("tofu", tc.G)
   162  	require.NoError(t, err)
   163  
   164  	clock := clockwork.NewFakeClock()
   165  	tc.G.SetClock(clock)
   166  
   167  	provider, cacheProvider := setupTestCacheProviders(t, tc)
   168  	mockProvider := provider.provider
   169  
   170  	contactList := []keybase1.Contact{
   171  		MakeContact("Joe",
   172  			MakePhoneComponent("Home", "+1111222"),
   173  			MakePhoneComponent("Work", "+199123"),
   174  			MakeEmailComponent("E-mail", "bob@keyba.se"),
   175  			MakeEmailComponent("E-mail 2", "b@keyba.se"),
   176  		),
   177  	}
   178  
   179  	mockProvider.PhoneNumbers["+1111222"] = MockLookupUser{UID: keybase1.UID("01ffffffffffffffffffffffffffff00"), Username: "bob"}
   180  
   181  	res1, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   182  	require.NoError(t, err)
   183  
   184  	// All components were looked up.
   185  	require.Equal(t, 4, provider.queryCount)
   186  
   187  	{
   188  		// Query again with provider disabled, all results should be fetched from cache.
   189  		provider.disabled = true
   190  
   191  		res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   192  		require.NoError(t, err)
   193  		require.Equal(t, res1, res)
   194  
   195  		provider.disabled = false
   196  		provider.queryCount = 0
   197  	}
   198  
   199  	{
   200  		// Push us over unresolved contact cache expiration time.
   201  		clock.Advance(25 * time.Hour) // see *MockContactsProvider::LookupAll for correct value
   202  
   203  		res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   204  		require.NoError(t, err)
   205  		require.Equal(t, res1, res)
   206  
   207  		// Expect to look up unresolved components (unresolved freshness is shorter than resolved)
   208  		require.Equal(t, 3, provider.queryCount)
   209  
   210  		provider.queryCount = 0
   211  	}
   212  
   213  	{
   214  		// Push us over resolved contact cache expiration time.
   215  		clock.Advance(10*24*time.Hour + time.Hour) // see *MockContactsProvider::LookupAll for correct value
   216  
   217  		res, err := ResolveContacts(libkb.NewMetaContextForTest(tc), cacheProvider, contactList)
   218  		require.NoError(t, err)
   219  		require.Equal(t, res1, res)
   220  
   221  		// Expect to look up all components.
   222  		require.Equal(t, 4, provider.queryCount)
   223  
   224  		provider.queryCount = 0
   225  	}
   226  
   227  	{
   228  		// Go really far forward to trigger cleanup. Test provider returns
   229  		// 10-day freshness for resolved entries and 1-day for unresolved. This
   230  		// combined with `minimumFreshness` time gives 55 days after which all
   231  		// current entries should be evicted.
   232  		clock.Advance(55*24*time.Hour + time.Hour)
   233  
   234  		mctx := libkb.NewMetaContextForTest(tc)
   235  		contactList := []keybase1.Contact{
   236  			MakeContact("Robert",
   237  				MakePhoneComponent("Phone", "+48111222333"),
   238  			),
   239  		}
   240  
   241  		res, err := ResolveContacts(mctx, cacheProvider, contactList)
   242  		require.NoError(t, err)
   243  		require.Len(t, res, 1)
   244  
   245  		// Expect to look up all components from new contactList.
   246  		require.Equal(t, 1, provider.queryCount)
   247  
   248  		// Old entries from previous lookups should have been cleared, only
   249  		// last lookup should be cached.
   250  		cacheObj, created := cacheProvider.Store.getCache(mctx)
   251  		require.False(t, created)
   252  		require.Len(t, cacheObj.Lookups, 1)
   253  		_, ok := cacheObj.Lookups[MakePhoneLookupKey("+48111222333")]
   254  		require.True(t, ok)
   255  	}
   256  
   257  }