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 }