github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/uidmap/servicemap_test.go (about) 1 package uidmap 2 3 import ( 4 "errors" 5 "reflect" 6 "testing" 7 "time" 8 9 "github.com/davecgh/go-spew/spew" 10 "github.com/keybase/client/go/libkb" 11 keybase1 "github.com/keybase/client/go/protocol/keybase1" 12 "github.com/keybase/clockwork" 13 "github.com/stretchr/testify/require" 14 "golang.org/x/net/context" 15 ) 16 17 type timeoutAPIMock struct { 18 libkb.API 19 callCount int 20 } 21 22 func (n *timeoutAPIMock) PostDecodeCtx(context.Context, libkb.APIArg, libkb.APIResponseWrapper) error { 23 n.callCount++ 24 return libkb.APINetError{Err: errors.New("timeoutAPIMock")} 25 } 26 27 const tTracy = keybase1.UID("eb72f49f2dde6429e5d78003dae0c919") 28 const tAlice = keybase1.UID("295a7eea607af32040647123732bc819") 29 30 func TestServiceMapLookupKnown(t *testing.T) { 31 tc := libkb.SetupTest(t, "TestLookup", 1) 32 defer tc.Cleanup() 33 34 fakeClock := clockwork.NewFakeClockAt(time.Now()) 35 tc.G.SetClock(fakeClock) 36 37 now := keybase1.ToTime(fakeClock.Now()) 38 39 serviceMapper := NewServiceSummaryMap(10) 40 uids := []keybase1.UID{tKB, tAlice, tTracy} 41 const zeroDuration = time.Duration(0) 42 pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 43 zeroDuration /* freshness */, DefaultNetworkBudget /* networkBudget */) 44 45 require.Len(t, pkgs, 3) 46 require.Contains(t, pkgs, tKB) 47 require.Contains(t, pkgs, tAlice) 48 require.Contains(t, pkgs, tTracy) 49 for _, v := range pkgs { 50 require.True(t, v.CachedAt >= now) 51 require.NotNil(t, v.ServiceMap) 52 } 53 54 // Exact maps depend on remote_identities on the test server. 55 require.Equal(t, "gbrltest", pkgs[tKB].ServiceMap["twitter"]) 56 require.Equal(t, "tacovontaco", pkgs[tAlice].ServiceMap["twitter"]) 57 require.Equal(t, "tacoplusplus", pkgs[tTracy].ServiceMap["github"]) 58 require.Equal(t, "t_tracy", pkgs[tTracy].ServiceMap["rooter"]) 59 require.Equal(t, "tacovontaco", pkgs[tTracy].ServiceMap["twitter"]) 60 61 timeoutAPI := &timeoutAPIMock{} 62 tc.G.API = timeoutAPI 63 64 // Doesn't matter for the API itself because we are mocking it, but make 65 // sure serviceMapper does not do anything funky when budget is anything 66 // other than default. 67 networkBudget := 1 * time.Second 68 69 { 70 pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 71 zeroDuration /* freshness */, networkBudget) 72 require.Equal(t, pkgs, pkgs2) 73 // Should not have to call API with freshness set to "always fresh". 74 require.Equal(t, 0, timeoutAPI.callCount) 75 } 76 77 { 78 // Same, but advance fake clock and provide `freshness` argument. We 79 // should fail to get data with always-timeouting API. 80 fakeClock.Advance(24 * time.Hour) 81 82 pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 83 12*time.Hour /* freshness */, networkBudget) 84 require.Len(t, pkgs2, 0) 85 require.Equal(t, 1, timeoutAPI.callCount) 86 } 87 88 { 89 // Similar, but with DisallowNetworkBudget which should skip request completely. 90 pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 91 12*time.Hour /* freshness */, DisallowNetworkBudget /* networkBudget */) 92 require.Len(t, pkgs2, 0) 93 require.Equal(t, 1, timeoutAPI.callCount) // same count as after previous call 94 } 95 } 96 97 func TestServiceMapLookupEmpty(t *testing.T) { 98 tc := libkb.SetupTest(t, "TestLookup", 1) 99 defer tc.Cleanup() 100 101 now := keybase1.ToTime(time.Now()) 102 serviceMapper := NewServiceSummaryMap(10) 103 104 const tFrank = keybase1.UID("359c7644857203be38bfd3bf79bf1819") 105 uids := []keybase1.UID{tFrank} 106 const zeroDuration = time.Duration(0) 107 pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 108 zeroDuration /* freshness */, zeroDuration /* networkBudget */) 109 110 // t_frank has no services, expecting to see t_frank in result map but with 111 // nil ServiceMap field. 112 require.Len(t, pkgs, 1) 113 require.Contains(t, pkgs, tFrank) 114 require.Nil(t, pkgs[tFrank].ServiceMap) 115 require.True(t, pkgs[tFrank].CachedAt >= now) 116 117 timeoutAPI := &timeoutAPIMock{} 118 tc.G.API = timeoutAPI 119 120 { 121 // Query again with very strict network budget hoping to hit cache. 122 pkgs2 := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 123 zeroDuration /* freshness */, DefaultNetworkBudget /* networkBudget */) 124 require.Equal(t, pkgs, pkgs2) 125 // should not hit the API due to freshness parameter. 126 require.Equal(t, 0, timeoutAPI.callCount) 127 } 128 } 129 130 type mockAPI struct { 131 libkb.API 132 t *testing.T 133 results map[keybase1.UID]libkb.UserServiceSummary 134 } 135 136 func (m *mockAPI) PostDecodeCtx(ctx context.Context, arg libkb.APIArg, resp libkb.APIResponseWrapper) error { 137 require.Equal(m.t, "user/service_maps", arg.Endpoint) 138 139 ps := reflect.ValueOf(resp) 140 elem := ps.Elem() 141 field := elem.FieldByName("ServiceMaps") 142 field.Set(reflect.ValueOf(m.results)) 143 144 m.t.Logf("mockAPI is returning for user/service_maps: %s", spew.Sdump(m.results)) 145 return nil 146 } 147 148 func TestServiceMapBecomesEmpty(t *testing.T) { 149 tc := libkb.SetupTest(t, "TestLookup", 1) 150 defer tc.Cleanup() 151 152 fakeClock := clockwork.NewFakeClockAt(time.Now()) 153 tc.G.SetClock(fakeClock) 154 155 now := keybase1.ToTime(fakeClock.Now()) 156 157 results := make(map[keybase1.UID]libkb.UserServiceSummary) 158 sumsum := make(libkb.UserServiceSummary) 159 sumsum["twitter"] = "tracy" 160 sumsum["github"] = "tracerz" 161 results[tTracy] = sumsum 162 apiMock := &mockAPI{nil, t, results} 163 164 tc.G.API = apiMock 165 166 const freshness = 24 * time.Hour 167 serviceMapper := NewServiceSummaryMap(10) 168 uids := []keybase1.UID{tTracy} 169 pkgs := serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 170 freshness, DefaultNetworkBudget) 171 172 require.Len(t, pkgs, 1) 173 require.Contains(t, pkgs, tTracy) 174 require.True(t, pkgs[tTracy].CachedAt >= now) 175 require.Equal(t, sumsum, pkgs[tTracy].ServiceMap) 176 177 // Now the service returns empty service map because someone has revoked 178 // all their proofs (or reset). 179 180 fakeClock.Advance(30 * time.Hour) 181 182 // Server returns empty result because user's proofs are all gone. 183 results = make(map[keybase1.UID]libkb.UserServiceSummary) 184 apiMock.results = results 185 186 // Do a normal call with network budget to allow for cache refresh. 187 pkgs = serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 188 freshness, DefaultNetworkBudget) 189 190 var nilMap libkb.UserServiceSummary 191 192 require.Len(t, pkgs, 1) 193 require.Contains(t, pkgs, tTracy) 194 require.True(t, pkgs[tTracy].CachedAt >= now) 195 require.Equal(t, nilMap, pkgs[tTracy].ServiceMap) 196 197 fakeClock.Advance(1 * time.Hour) 198 199 // Now do a call without network budget to force read from cache. 200 pkgs = serviceMapper.MapUIDsToServiceSummaries(context.TODO(), tc.G, uids, 201 freshness, DisallowNetworkBudget) 202 203 require.Len(t, pkgs, 1) 204 require.Contains(t, pkgs, tTracy) 205 require.True(t, pkgs[tTracy].CachedAt >= now) 206 require.Equal(t, nilMap, pkgs[tTracy].ServiceMap) 207 }