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  }