github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/tlfhandle/handle_test.go (about)

     1  // Copyright 2016 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package tlfhandle
     6  
     7  import (
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/keybase/client/go/kbfs/idutil"
    13  	idutiltest "github.com/keybase/client/go/kbfs/idutil/test"
    14  	"github.com/keybase/client/go/kbfs/kbfscodec"
    15  	"github.com/keybase/client/go/kbfs/tlf"
    16  	kbname "github.com/keybase/client/go/kbun"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  	"github.com/pkg/errors"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"golang.org/x/net/context"
    22  )
    23  
    24  func TestParseHandleEarlyFailure(t *testing.T) {
    25  	ctx := context.Background()
    26  
    27  	name := "w1,w2#r1"
    28  	_, err := ParseHandle(ctx, nil, nil, nil, name, tlf.Public)
    29  	assert.Equal(t, idutil.NoSuchNameError{Name: name}, err)
    30  
    31  	nonCanonicalName := "W1,w2#r1"
    32  	_, err = ParseHandle(ctx, nil, nil, nil, nonCanonicalName, tlf.Private)
    33  	assert.Equal(
    34  		t, idutil.TlfNameNotCanonical{
    35  			Name: nonCanonicalName, NameToTry: name}, errors.Cause(err))
    36  }
    37  
    38  func TestParseHandleNoUserFailure(t *testing.T) {
    39  	ctx := context.Background()
    40  
    41  	localUsers := idutil.MakeLocalUsers(
    42  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
    43  	currentUID := localUsers[0].UID
    44  	daemon := idutil.NewDaemonLocal(
    45  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
    46  
    47  	kbpki := &idutiltest.IdentifyCountingKBPKI{
    48  		KBPKI: &idutiltest.DaemonKBPKI{
    49  			Daemon: daemon,
    50  		},
    51  	}
    52  
    53  	name := "u2,u3#u4"
    54  	_, err := ParseHandle(ctx, kbpki, nil, nil, name, tlf.Private)
    55  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
    56  	assert.Equal(t, idutil.NoSuchUserError{Input: "u4"}, err)
    57  }
    58  
    59  func TestParseHandleNotReaderFailure(t *testing.T) {
    60  	ctx := context.Background()
    61  
    62  	localUsers := idutil.MakeLocalUsers(
    63  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
    64  	currentUID := localUsers[0].UID
    65  	daemon := idutil.NewDaemonLocal(
    66  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
    67  
    68  	kbpki := &idutiltest.IdentifyCountingKBPKI{
    69  		KBPKI: &idutiltest.DaemonKBPKI{
    70  			Daemon: daemon,
    71  		},
    72  	}
    73  
    74  	name := "u2,u3"
    75  	_, err := ParseHandle(
    76  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil, name,
    77  		tlf.Private)
    78  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
    79  	assert.Equal(t, ReadAccessError{User: "u1", Tlf: tlf.CanonicalName(name), Type: tlf.Private, Filename: "/keybase/private/u2,u3"}, err)
    80  }
    81  
    82  func TestParseHandleSingleTeam(t *testing.T) {
    83  	ctx := context.Background()
    84  
    85  	localUsers := idutil.MakeLocalUsers([]kbname.NormalizedUsername{"u1"})
    86  	currentUID := localUsers[0].UID
    87  	localTeams := idutil.MakeLocalTeams([]kbname.NormalizedUsername{"t1"})
    88  	daemon := idutil.NewDaemonLocal(
    89  		currentUID, localUsers, localTeams, kbfscodec.NewMsgpack())
    90  
    91  	tlfID := tlf.FakeID(0, tlf.SingleTeam)
    92  	err := daemon.CreateTeamTLF(ctx, localTeams[0].TID, tlfID)
    93  	require.NoError(t, err)
    94  
    95  	kbpki := &idutiltest.IdentifyCountingKBPKI{
    96  		KBPKI: &idutiltest.DaemonKBPKI{
    97  			Daemon: daemon,
    98  		},
    99  	}
   100  
   101  	h, err := ParseHandle(
   102  		ctx, kbpki, ConstIDGetter{tlfID}, nil, "t1", tlf.SingleTeam)
   103  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
   104  	require.NoError(t, err)
   105  	require.Equal(t, tlfID, h.tlfID)
   106  }
   107  
   108  func TestParseHandleSingleTeamFailures(t *testing.T) {
   109  	ctx := context.Background()
   110  
   111  	localUsers := idutil.MakeLocalUsers(
   112  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   113  	currentUID := localUsers[0].UID
   114  	localTeams := idutil.MakeLocalTeams(
   115  		[]kbname.NormalizedUsername{"t1", "t2"})
   116  	daemon := idutil.NewDaemonLocal(
   117  		currentUID, localUsers, localTeams, kbfscodec.NewMsgpack())
   118  
   119  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   120  		KBPKI: &idutiltest.DaemonKBPKI{
   121  			Daemon: daemon,
   122  		},
   123  	}
   124  
   125  	_, err := ParseHandle(ctx, kbpki, nil, nil, "u1", tlf.SingleTeam)
   126  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
   127  	assert.Equal(t, idutil.NoSuchUserError{Input: "u1@team"}, err)
   128  
   129  	checkNoSuchName := func(name string, ty tlf.Type) {
   130  		_, err := ParseHandle(ctx, kbpki, nil, nil, name, ty)
   131  		assert.Equal(t, 0, kbpki.GetIdentifyCalls())
   132  		if ty == tlf.SingleTeam {
   133  			assert.Equal(t, idutil.NoSuchNameError{Name: name}, err)
   134  		} else {
   135  			assert.Equal(t, idutil.NoSuchUserError{Input: "t1"}, err)
   136  		}
   137  	}
   138  
   139  	checkNoSuchName("t1,u1", tlf.SingleTeam)
   140  	checkNoSuchName("u1,t1", tlf.SingleTeam)
   141  	checkNoSuchName("t1,t2", tlf.SingleTeam)
   142  	checkNoSuchName("t1#t2", tlf.SingleTeam)
   143  	checkNoSuchName("t1", tlf.Private)
   144  	checkNoSuchName("t1,u1", tlf.Private)
   145  	checkNoSuchName("u1#t1", tlf.Private)
   146  	checkNoSuchName("t1#u1", tlf.Private)
   147  	checkNoSuchName("t1", tlf.Public)
   148  	checkNoSuchName("t1,u1", tlf.Public)
   149  }
   150  
   151  func TestParseHandleAssertionNotCanonicalFailure(t *testing.T) {
   152  	ctx := context.Background()
   153  
   154  	localUsers := idutil.MakeLocalUsers(
   155  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   156  	localUsers[2].Asserts = []string{"u3@twitter"}
   157  	currentUID := localUsers[0].UID
   158  	daemon := idutil.NewDaemonLocal(
   159  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   160  
   161  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   162  		KBPKI: &idutiltest.DaemonKBPKI{
   163  			Daemon: daemon,
   164  		},
   165  	}
   166  
   167  	name := "u1,u3#u2"
   168  	nonCanonicalName := "u1,u3@twitter#u2"
   169  	_, err := ParseHandle(
   170  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   171  		nonCanonicalName, tlf.Private)
   172  	// Names with assertions should be identified before the error
   173  	// is returned.
   174  	assert.Equal(t, 3, kbpki.GetIdentifyCalls())
   175  	assert.Equal(
   176  		t, idutil.TlfNameNotCanonical{Name: nonCanonicalName, NameToTry: name},
   177  		errors.Cause(err))
   178  }
   179  
   180  func TestParseHandleAssertionPrivateSuccess(t *testing.T) {
   181  	ctx := context.Background()
   182  
   183  	localUsers := idutil.MakeLocalUsers(
   184  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   185  	currentUID := localUsers[0].UID
   186  	daemon := idutil.NewDaemonLocal(
   187  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   188  
   189  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   190  		KBPKI: &idutiltest.DaemonKBPKI{
   191  			Daemon: daemon,
   192  		},
   193  	}
   194  
   195  	name := "u1,u3"
   196  	h, err := ParseHandle(
   197  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil, name,
   198  		tlf.Private)
   199  	require.NoError(t, err)
   200  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
   201  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   202  
   203  	// Make sure that generating another handle doesn't change the
   204  	// name.
   205  	h2, err := MakeHandle(
   206  		context.Background(), h.ToBareHandleOrBust(), tlf.Private,
   207  		kbpki, kbpki, nil, keybase1.OfflineAvailability_NONE)
   208  	require.NoError(t, err)
   209  	assert.Equal(t, tlf.CanonicalName(name), h2.GetCanonicalName())
   210  }
   211  
   212  func TestParseHandleAssertionPublicSuccess(t *testing.T) {
   213  	ctx := context.Background()
   214  
   215  	localUsers := idutil.MakeLocalUsers(
   216  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   217  	currentUID := localUsers[0].UID
   218  	daemon := idutil.NewDaemonLocal(
   219  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   220  
   221  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   222  		KBPKI: &idutiltest.DaemonKBPKI{
   223  			Daemon: daemon,
   224  		},
   225  	}
   226  
   227  	name := "u1,u2,u3"
   228  	h, err := ParseHandle(
   229  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Public)}, nil,
   230  		name, tlf.Public)
   231  	require.NoError(t, err)
   232  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
   233  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   234  
   235  	// Make sure that generating another handle doesn't change the
   236  	// name.
   237  	h2, err := MakeHandle(
   238  		context.Background(), h.ToBareHandleOrBust(), tlf.Public,
   239  		kbpki, kbpki, nil, keybase1.OfflineAvailability_NONE)
   240  	require.NoError(t, err)
   241  	assert.Equal(t, tlf.CanonicalName(name), h2.GetCanonicalName())
   242  }
   243  
   244  func TestHandleAccessorsPrivate(t *testing.T) {
   245  	ctx := context.Background()
   246  
   247  	localUsers := idutil.MakeLocalUsers(
   248  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   249  	currentUID := localUsers[0].UID
   250  	daemon := idutil.NewDaemonLocal(
   251  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   252  
   253  	kbpki := &idutiltest.DaemonKBPKI{
   254  		Daemon: daemon,
   255  	}
   256  
   257  	name := "u1,u2@twitter,u3,u4@twitter#u2,u5@twitter,u6@twitter"
   258  	h, err := ParseHandle(
   259  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   260  		name, tlf.Private)
   261  	require.NoError(t, err)
   262  
   263  	require.False(t, h.Type() == tlf.Public)
   264  
   265  	require.True(t, h.IsWriter(localUsers[0].UID))
   266  	require.True(t, h.IsReader(localUsers[0].UID))
   267  
   268  	require.False(t, h.IsWriter(localUsers[1].UID))
   269  	require.True(t, h.IsReader(localUsers[1].UID))
   270  
   271  	require.True(t, h.IsWriter(localUsers[2].UID))
   272  	require.True(t, h.IsReader(localUsers[2].UID))
   273  
   274  	for i := 6; i < 10; i++ {
   275  		u := keybase1.MakeTestUID(uint32(i))
   276  		require.False(t, h.IsWriter(u))
   277  		require.False(t, h.IsReader(u))
   278  	}
   279  
   280  	require.Equal(t, h.ResolvedWriters(),
   281  		[]keybase1.UserOrTeamID{
   282  			localUsers[0].UID.AsUserOrTeam(),
   283  			localUsers[2].UID.AsUserOrTeam(),
   284  		})
   285  	require.Equal(t, h.FirstResolvedWriter(), localUsers[0].UID.AsUserOrTeam())
   286  
   287  	require.Equal(t, h.ResolvedReaders(),
   288  		[]keybase1.UserOrTeamID{
   289  			localUsers[1].UID.AsUserOrTeam(),
   290  		})
   291  
   292  	require.Equal(t, h.UnresolvedWriters(),
   293  		[]keybase1.SocialAssertion{
   294  			{
   295  				User:    "u2",
   296  				Service: "twitter",
   297  			},
   298  			{
   299  				User:    "u4",
   300  				Service: "twitter",
   301  			},
   302  		})
   303  	require.Equal(t, h.UnresolvedReaders(),
   304  		[]keybase1.SocialAssertion{
   305  			{
   306  				User:    "u5",
   307  				Service: "twitter",
   308  			},
   309  			{
   310  				User:    "u6",
   311  				Service: "twitter",
   312  			},
   313  		})
   314  }
   315  
   316  func TestHandleAccessorsPublic(t *testing.T) {
   317  	ctx := context.Background()
   318  
   319  	localUsers := idutil.MakeLocalUsers(
   320  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   321  	currentUID := localUsers[0].UID
   322  	daemon := idutil.NewDaemonLocal(
   323  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   324  
   325  	kbpki := &idutiltest.DaemonKBPKI{
   326  		Daemon: daemon,
   327  	}
   328  
   329  	name := "u1,u2@twitter,u3,u4@twitter"
   330  	h, err := ParseHandle(
   331  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Public)}, nil,
   332  		name, tlf.Public)
   333  	require.NoError(t, err)
   334  
   335  	require.True(t, h.Type() == tlf.Public)
   336  
   337  	require.True(t, h.IsWriter(localUsers[0].UID))
   338  	require.True(t, h.IsReader(localUsers[0].UID))
   339  
   340  	require.False(t, h.IsWriter(localUsers[1].UID))
   341  	require.True(t, h.IsReader(localUsers[1].UID))
   342  
   343  	require.True(t, h.IsWriter(localUsers[2].UID))
   344  	require.True(t, h.IsReader(localUsers[2].UID))
   345  
   346  	for i := 6; i < 10; i++ {
   347  		u := keybase1.MakeTestUID(uint32(i))
   348  		require.False(t, h.IsWriter(u))
   349  		require.True(t, h.IsReader(u))
   350  	}
   351  
   352  	require.Equal(t, h.ResolvedWriters(),
   353  		[]keybase1.UserOrTeamID{
   354  			localUsers[0].UID.AsUserOrTeam(),
   355  			localUsers[2].UID.AsUserOrTeam(),
   356  		})
   357  	require.Equal(t, h.FirstResolvedWriter(), localUsers[0].UID.AsUserOrTeam())
   358  
   359  	require.Nil(t, h.ResolvedReaders())
   360  
   361  	require.Equal(t, h.UnresolvedWriters(),
   362  		[]keybase1.SocialAssertion{
   363  			{
   364  				User:    "u2",
   365  				Service: "twitter",
   366  			},
   367  			{
   368  				User:    "u4",
   369  				Service: "twitter",
   370  			},
   371  		})
   372  	require.Nil(t, h.UnresolvedReaders())
   373  }
   374  
   375  func TestHandleConflictInfo(t *testing.T) {
   376  	ctx := context.Background()
   377  
   378  	localUsers := idutil.MakeLocalUsers(
   379  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   380  	currentUID := localUsers[0].UID
   381  	codec := kbfscodec.NewMsgpack()
   382  	daemon := idutil.NewDaemonLocal(currentUID, localUsers, nil, codec)
   383  	kbpki := &idutiltest.DaemonKBPKI{
   384  		Daemon: daemon,
   385  	}
   386  
   387  	name := "u1,u2,u3"
   388  	cname := tlf.CanonicalName(name)
   389  	h, err := ParseHandle(ctx, kbpki, nil, nil, name, tlf.Public)
   390  	require.NoError(t, err)
   391  
   392  	require.Nil(t, h.ConflictInfo())
   393  
   394  	h, err = h.WithUpdatedConflictInfo(codec, nil)
   395  	require.NoError(t, err)
   396  	require.Equal(t, h.GetCanonicalName(), cname)
   397  
   398  	info := tlf.HandleExtension{
   399  		Date:   100,
   400  		Number: 50,
   401  		Type:   tlf.HandleExtensionConflict,
   402  	}
   403  	h, err = h.WithUpdatedConflictInfo(codec, &info)
   404  	require.NoError(t, err)
   405  	require.Equal(t, info, *h.ConflictInfo())
   406  	cname2 := tlf.CanonicalName(name + tlf.HandleExtensionSep + info.String())
   407  	require.Equal(t, h.GetCanonicalName(), cname2)
   408  
   409  	info.Date = 101
   410  	require.NotEqual(t, info, *h.ConflictInfo())
   411  
   412  	info.Date = 100
   413  	h, err = h.WithUpdatedConflictInfo(codec, &info)
   414  	cname3 := tlf.CanonicalName(name + tlf.HandleExtensionSep + info.String())
   415  	require.NoError(t, err)
   416  	require.Equal(t, h.GetCanonicalName(), cname3)
   417  
   418  	expectedErr := tlf.HandleExtensionMismatchError{
   419  		Expected: *h.ConflictInfo(),
   420  		Actual:   nil,
   421  	}
   422  	h, err = h.WithUpdatedConflictInfo(codec, nil)
   423  	require.Equal(t, expectedErr, err)
   424  	require.Equal(t, "Folder handle extension mismatch, expected: (conflicted copy 1970-01-01 #50), actual: <nil>", err.Error())
   425  
   426  	expectedErr = tlf.HandleExtensionMismatchError{
   427  		Expected: *h.ConflictInfo(),
   428  		Actual:   &info,
   429  	}
   430  	info.Date = 101
   431  	_, err = h.WithUpdatedConflictInfo(codec, &info)
   432  	require.Equal(t, expectedErr, err)
   433  	// A strange error message, since the difference doesn't show
   434  	// up in the strings. Oh, well.
   435  	require.Equal(t, "Folder handle extension mismatch, expected: (conflicted copy 1970-01-01 #50), actual: (conflicted copy 1970-01-01 #50)", err.Error())
   436  }
   437  
   438  func TestHandleFinalizedInfo(t *testing.T) {
   439  	ctx := context.Background()
   440  
   441  	localUsers := idutil.MakeLocalUsers(
   442  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   443  	currentUID := localUsers[0].UID
   444  	codec := kbfscodec.NewMsgpack()
   445  	daemon := idutil.NewDaemonLocal(currentUID, localUsers, nil, codec)
   446  	kbpki := &idutiltest.DaemonKBPKI{
   447  		Daemon: daemon,
   448  	}
   449  
   450  	name := "u1,u2,u3"
   451  	cname := tlf.CanonicalName(name)
   452  	h, err := ParseHandle(ctx, kbpki, nil, nil, name, tlf.Public)
   453  	require.NoError(t, err)
   454  
   455  	require.Nil(t, h.FinalizedInfo())
   456  	info := tlf.HandleExtension{
   457  		Date:   100,
   458  		Number: 50,
   459  		Type:   tlf.HandleExtensionFinalized,
   460  	}
   461  
   462  	h.SetFinalizedInfo(&info)
   463  	require.Equal(t, info, *h.FinalizedInfo())
   464  	cname2 := tlf.CanonicalName(name + tlf.HandleExtensionSep + info.String())
   465  	require.Equal(t, h.GetCanonicalName(), cname2)
   466  
   467  	info.Date = 101
   468  	require.NotEqual(t, info, *h.FinalizedInfo())
   469  
   470  	h.SetFinalizedInfo(nil)
   471  	require.Nil(t, h.FinalizedInfo())
   472  	require.Equal(t, h.GetCanonicalName(), cname)
   473  }
   474  
   475  func TestHandleConflictAndFinalizedInfo(t *testing.T) {
   476  	ctx := context.Background()
   477  
   478  	localUsers := idutil.MakeLocalUsers(
   479  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   480  	currentUID := localUsers[0].UID
   481  	codec := kbfscodec.NewMsgpack()
   482  	daemon := idutil.NewDaemonLocal(currentUID, localUsers, nil, codec)
   483  	kbpki := &idutiltest.DaemonKBPKI{
   484  		Daemon: daemon,
   485  	}
   486  
   487  	name := "u1,u2,u3"
   488  	h, err := ParseHandle(ctx, kbpki, nil, nil, name, tlf.Public)
   489  	require.NoError(t, err)
   490  
   491  	require.Nil(t, h.ConflictInfo())
   492  
   493  	cInfo := tlf.HandleExtension{
   494  		Date:   100,
   495  		Number: 50,
   496  		Type:   tlf.HandleExtensionConflict,
   497  	}
   498  	h, err = h.WithUpdatedConflictInfo(codec, &cInfo)
   499  	require.NoError(t, err)
   500  	require.Equal(t, cInfo, *h.ConflictInfo())
   501  	cname2 := tlf.CanonicalName(name + tlf.HandleExtensionSep + cInfo.String())
   502  	require.Equal(t, h.GetCanonicalName(), cname2)
   503  
   504  	fInfo := tlf.HandleExtension{
   505  		Date:   101,
   506  		Number: 51,
   507  		Type:   tlf.HandleExtensionFinalized,
   508  	}
   509  	h.SetFinalizedInfo(&fInfo)
   510  	require.Equal(t, fInfo, *h.FinalizedInfo())
   511  	require.Equal(t, cInfo, *h.ConflictInfo())
   512  	cname3 := cname2 + tlf.CanonicalName(tlf.HandleExtensionSep+fInfo.String())
   513  	require.Equal(t, h.GetCanonicalName(), cname3)
   514  }
   515  
   516  func TestTlfHandlEqual(t *testing.T) {
   517  	ctx := context.Background()
   518  
   519  	localUsers := idutil.MakeLocalUsers(
   520  		[]kbname.NormalizedUsername{"u1", "u2", "u3", "u4", "u5"})
   521  	currentUID := localUsers[0].UID
   522  	codec := kbfscodec.NewMsgpack()
   523  	daemon := idutil.NewDaemonLocal(currentUID, localUsers, nil, codec)
   524  
   525  	kbpki := &idutiltest.DaemonKBPKI{
   526  		Daemon: daemon,
   527  	}
   528  
   529  	name1 := "u1,u2@twitter,u3,u4@twitter"
   530  	h1, err := ParseHandle(ctx, kbpki, nil, nil, name1, tlf.Public)
   531  	require.NoError(t, err)
   532  
   533  	eq, err := h1.Equals(codec, *h1)
   534  	require.NoError(t, err)
   535  	require.True(t, eq)
   536  
   537  	// Test public bit.
   538  
   539  	h2, err := ParseHandle(ctx, kbpki, nil, nil, name1, tlf.Private)
   540  	require.NoError(t, err)
   541  	eq, err = h1.Equals(codec, *h2)
   542  	require.NoError(t, err)
   543  	require.False(t, eq)
   544  
   545  	// Test resolved and unresolved readers and writers.
   546  
   547  	name1 = "u1,u2@twitter#u3,u4@twitter"
   548  	h1, err = ParseHandle(ctx, kbpki, nil, nil, name1, tlf.Private)
   549  	require.NoError(t, err)
   550  
   551  	for _, name2 := range []string{
   552  		"u1,u2@twitter,u5#u3,u4@twitter",
   553  		"u1,u5@twitter#u3,u4@twitter",
   554  		"u1,u2@twitter#u4@twitter,u5",
   555  		"u1,u2@twitter#u3,u5@twitter",
   556  	} {
   557  		h2, err := ParseHandle(ctx, kbpki, nil, nil, name2, tlf.Private)
   558  		require.NoError(t, err)
   559  		eq, err := h1.Equals(codec, *h2)
   560  		require.NoError(t, err)
   561  		require.False(t, eq)
   562  	}
   563  
   564  	// Test conflict info and finalized info.
   565  
   566  	h2, err = ParseHandle(ctx, kbpki, nil, nil, name1, tlf.Private)
   567  	require.NoError(t, err)
   568  	info := tlf.HandleExtension{
   569  		Date:   100,
   570  		Number: 50,
   571  		Type:   tlf.HandleExtensionConflict,
   572  	}
   573  	h2, err = h2.WithUpdatedConflictInfo(codec, &info)
   574  	require.NoError(t, err)
   575  
   576  	eq, err = h1.Equals(codec, *h2)
   577  	require.NoError(t, err)
   578  	require.False(t, eq)
   579  
   580  	h2, err = ParseHandle(ctx, kbpki, nil, nil, name1, tlf.Private)
   581  	require.NoError(t, err)
   582  	h2.SetFinalizedInfo(&info)
   583  
   584  	eq, err = h1.Equals(codec, *h2)
   585  	require.NoError(t, err)
   586  	require.False(t, eq)
   587  
   588  	// Test failure on name difference.
   589  	h2, err = ParseHandle(ctx, kbpki, nil, nil, name1, tlf.Private)
   590  	require.NoError(t, err)
   591  	h2.name += "x"
   592  	eq, err = h1.Equals(codec, *h2)
   593  	require.NoError(t, err)
   594  	require.False(t, eq)
   595  }
   596  
   597  func TestParseHandleSocialAssertion(t *testing.T) {
   598  	ctx := context.Background()
   599  
   600  	localUsers := idutil.MakeLocalUsers(
   601  		[]kbname.NormalizedUsername{"u1", "u2"})
   602  	currentUID := localUsers[0].UID
   603  	daemon := idutil.NewDaemonLocal(
   604  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   605  
   606  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   607  		KBPKI: &idutiltest.DaemonKBPKI{
   608  			Daemon: daemon,
   609  		},
   610  	}
   611  
   612  	name := "u1,u2#u3@twitter"
   613  	h, err := ParseHandle(
   614  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   615  		name, tlf.Private)
   616  	assert.Equal(t, 0, kbpki.GetIdentifyCalls())
   617  	require.NoError(t, err)
   618  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   619  
   620  	// Make sure that generating another handle doesn't change the
   621  	// name.
   622  	h2, err := MakeHandle(
   623  		context.Background(), h.ToBareHandleOrBust(), tlf.Private,
   624  		kbpki, kbpki, nil, keybase1.OfflineAvailability_NONE)
   625  	require.NoError(t, err)
   626  	assert.Equal(t, tlf.CanonicalName(name), h2.GetCanonicalName())
   627  }
   628  
   629  func TestParseHandleUIDAssertion(t *testing.T) {
   630  	ctx := context.Background()
   631  
   632  	localUsers := idutil.MakeLocalUsers(
   633  		[]kbname.NormalizedUsername{"u1", "u2"})
   634  	currentUID := localUsers[0].UID
   635  	daemon := idutil.NewDaemonLocal(
   636  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   637  
   638  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   639  		KBPKI: &idutiltest.DaemonKBPKI{
   640  			Daemon: daemon,
   641  		},
   642  	}
   643  
   644  	a := currentUID.String() + "@uid"
   645  	_, err := ParseHandle(
   646  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   647  		a, tlf.Private)
   648  	assert.Equal(t, 1, kbpki.GetIdentifyCalls())
   649  	assert.Equal(t, idutil.TlfNameNotCanonical{
   650  		Name: a, NameToTry: "u1"}, errors.Cause(err))
   651  }
   652  
   653  func TestParseHandleAndAssertion(t *testing.T) {
   654  	ctx := context.Background()
   655  
   656  	localUsers := idutil.MakeLocalUsers(
   657  		[]kbname.NormalizedUsername{"u1", "u2"})
   658  	localUsers[0].Asserts = []string{"u1@twitter"}
   659  	currentUID := localUsers[0].UID
   660  	daemon := idutil.NewDaemonLocal(
   661  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   662  
   663  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   664  		KBPKI: &idutiltest.DaemonKBPKI{
   665  			Daemon: daemon,
   666  		},
   667  	}
   668  
   669  	a := currentUID.String() + "@uid+u1@twitter"
   670  	_, err := ParseHandle(
   671  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   672  		a, tlf.Private)
   673  	// We expect 1 extra identify for compound assertions until
   674  	// KBFS-2022 is completed.
   675  	assert.Equal(t, 1+1, kbpki.GetIdentifyCalls())
   676  	assert.Equal(t, idutil.TlfNameNotCanonical{
   677  		Name: a, NameToTry: "u1"}, errors.Cause(err))
   678  }
   679  
   680  func TestParseHandleConflictSuffix(t *testing.T) {
   681  	ctx := context.Background()
   682  
   683  	localUsers := idutil.MakeLocalUsers([]kbname.NormalizedUsername{"u1"})
   684  	currentUID := localUsers[0].UID
   685  	daemon := idutil.NewDaemonLocal(
   686  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   687  
   688  	kbpki := &idutiltest.DaemonKBPKI{
   689  		Daemon: daemon,
   690  	}
   691  
   692  	ci := &tlf.HandleExtension{
   693  		Date:   1462838400,
   694  		Number: 1,
   695  		Type:   tlf.HandleExtensionConflict,
   696  	}
   697  
   698  	a := "u1 " + ci.String()
   699  	h, err := ParseHandle(
   700  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   701  		a, tlf.Private)
   702  	require.NoError(t, err)
   703  	require.NotNil(t, h.ConflictInfo())
   704  	require.Equal(t, ci.String(), h.ConflictInfo().String())
   705  }
   706  
   707  func TestParseHandleFailConflictingAssertion(t *testing.T) {
   708  	ctx := context.Background()
   709  
   710  	localUsers := idutil.MakeLocalUsers(
   711  		[]kbname.NormalizedUsername{"u1", "u2"})
   712  	localUsers[1].Asserts = []string{"u2@twitter"}
   713  	currentUID := localUsers[0].UID
   714  	daemon := idutil.NewDaemonLocal(
   715  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   716  
   717  	kbpki := &idutiltest.IdentifyCountingKBPKI{
   718  		KBPKI: &idutiltest.DaemonKBPKI{
   719  			Daemon: daemon,
   720  		},
   721  	}
   722  
   723  	a := currentUID.String() + "@uid+u2@twitter"
   724  	_, err := ParseHandle(
   725  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   726  		a, tlf.Private)
   727  	// We expect 1 extra identify for compound assertions until
   728  	// KBFS-2022 is completed.
   729  	assert.Equal(t, 0+1, kbpki.GetIdentifyCalls())
   730  	require.Error(t, err)
   731  }
   732  
   733  func TestResolveAgainBasic(t *testing.T) {
   734  	ctx := context.Background()
   735  
   736  	localUsers := idutil.MakeLocalUsers(
   737  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   738  	currentUID := localUsers[0].UID
   739  	daemon := idutil.NewDaemonLocal(
   740  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   741  
   742  	kbpki := &idutiltest.DaemonKBPKI{
   743  		Daemon: daemon,
   744  	}
   745  
   746  	name := "u1,u2#u3@twitter"
   747  	h, err := ParseHandle(
   748  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   749  		name, tlf.Private)
   750  	require.NoError(t, err)
   751  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   752  
   753  	// ResolveAgain shouldn't rely on resolving the original names again.
   754  	daemon.AddNewAssertionForTestOrBust("u3", "u3@twitter")
   755  	newH, err := h.ResolveAgain(ctx, kbpki, nil, nil)
   756  	require.NoError(t, err)
   757  	assert.Equal(t, tlf.CanonicalName("u1,u2#u3"), newH.GetCanonicalName())
   758  }
   759  
   760  func TestResolveAgainDoubleAsserts(t *testing.T) {
   761  	ctx := context.Background()
   762  
   763  	localUsers := idutil.MakeLocalUsers(
   764  		[]kbname.NormalizedUsername{"u1", "u2"})
   765  	currentUID := localUsers[0].UID
   766  	daemon := idutil.NewDaemonLocal(
   767  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   768  
   769  	kbpki := &idutiltest.DaemonKBPKI{
   770  		Daemon: daemon,
   771  	}
   772  
   773  	name := "u1,u1@github,u1@twitter#u2,u2@github,u2@twitter"
   774  	h, err := ParseHandle(
   775  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   776  		name, tlf.Private)
   777  	require.NoError(t, err)
   778  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   779  
   780  	daemon.AddNewAssertionForTestOrBust("u1", "u1@twitter")
   781  	daemon.AddNewAssertionForTestOrBust("u1", "u1@github")
   782  	daemon.AddNewAssertionForTestOrBust("u2", "u2@twitter")
   783  	daemon.AddNewAssertionForTestOrBust("u2", "u2@github")
   784  	newH, err := h.ResolveAgain(ctx, kbpki, nil, nil)
   785  	require.NoError(t, err)
   786  	assert.Equal(t, tlf.CanonicalName("u1#u2"), newH.GetCanonicalName())
   787  }
   788  
   789  func TestResolveAgainWriterReader(t *testing.T) {
   790  	ctx := context.Background()
   791  
   792  	localUsers := idutil.MakeLocalUsers(
   793  		[]kbname.NormalizedUsername{"u1", "u2"})
   794  	currentUID := localUsers[0].UID
   795  	daemon := idutil.NewDaemonLocal(
   796  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   797  
   798  	kbpki := &idutiltest.DaemonKBPKI{
   799  		Daemon: daemon,
   800  	}
   801  
   802  	name := "u1,u2@github#u2@twitter"
   803  	h, err := ParseHandle(
   804  		ctx, kbpki, ConstIDGetter{tlf.FakeID(1, tlf.Private)}, nil,
   805  		name, tlf.Private)
   806  	require.NoError(t, err)
   807  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   808  
   809  	daemon.AddNewAssertionForTestOrBust("u2", "u2@twitter")
   810  	daemon.AddNewAssertionForTestOrBust("u2", "u2@github")
   811  	newH, err := h.ResolveAgain(ctx, kbpki, nil, nil)
   812  	require.NoError(t, err)
   813  	assert.Equal(t, tlf.CanonicalName("u1,u2"), newH.GetCanonicalName())
   814  }
   815  
   816  func TestResolveAgainConflict(t *testing.T) {
   817  	ctx := context.Background()
   818  
   819  	localUsers := idutil.MakeLocalUsers(
   820  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
   821  	currentUID := localUsers[0].UID
   822  	daemon := idutil.NewDaemonLocal(
   823  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
   824  
   825  	kbpki := &idutiltest.DaemonKBPKI{
   826  		Daemon: daemon,
   827  	}
   828  
   829  	name := "u1,u2#u3@twitter"
   830  	id := tlf.FakeID(1, tlf.Private)
   831  	h, err := ParseHandle(
   832  		ctx, kbpki, ConstIDGetter{id}, nil, name, tlf.Private)
   833  	require.NoError(t, err)
   834  	assert.Equal(t, tlf.CanonicalName(name), h.GetCanonicalName())
   835  
   836  	daemon.AddNewAssertionForTestOrBust("u3", "u3@twitter")
   837  	ext, err := tlf.NewHandleExtension(tlf.HandleExtensionConflict, 1, "", time.Now())
   838  	if err != nil {
   839  		t.Fatal(err)
   840  	}
   841  	h.conflictInfo = ext
   842  	newH, err := h.ResolveAgain(ctx, kbpki, nil, nil)
   843  	require.NoError(t, err)
   844  	assert.Equal(t, tlf.CanonicalName("u1,u2#u3"+
   845  		tlf.HandleExtensionSep+ext.String()), newH.GetCanonicalName())
   846  }
   847  
   848  func TestHandleResolvesTo(t *testing.T) {
   849  	ctx := context.Background()
   850  
   851  	localUsers := idutil.MakeLocalUsers(
   852  		[]kbname.NormalizedUsername{"u1", "u2", "u3", "u4", "u5"})
   853  	currentUID := localUsers[0].UID
   854  	codec := kbfscodec.NewMsgpack()
   855  	daemon := idutil.NewDaemonLocal(currentUID, localUsers, nil, codec)
   856  
   857  	kbpki := &idutiltest.DaemonKBPKI{
   858  		Daemon: daemon,
   859  	}
   860  
   861  	name1 := "u1,u2@twitter,u3,u4@twitter"
   862  	idPub := tlf.FakeID(1, tlf.Public)
   863  	h1, err := ParseHandle(
   864  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   865  	require.NoError(t, err)
   866  
   867  	resolvesTo, partialResolvedH1, err :=
   868  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h1)
   869  	require.NoError(t, err)
   870  	require.True(t, resolvesTo)
   871  	require.Equal(t, h1, partialResolvedH1)
   872  
   873  	// Test different public bit.
   874  
   875  	id := tlf.FakeID(1, tlf.Private)
   876  	h2, err := ParseHandle(
   877  		ctx, kbpki, ConstIDGetter{id}, nil, name1, tlf.Private)
   878  	require.NoError(t, err)
   879  
   880  	resolvesTo, partialResolvedH1, err =
   881  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2)
   882  	require.NoError(t, err)
   883  	require.False(t, resolvesTo)
   884  	require.Equal(t, h1, partialResolvedH1)
   885  
   886  	// Test adding conflict info or finalized info.
   887  
   888  	h2, err = ParseHandle(
   889  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   890  	require.NoError(t, err)
   891  	info := tlf.HandleExtension{
   892  		Date:   100,
   893  		Number: 50,
   894  		Type:   tlf.HandleExtensionConflict,
   895  	}
   896  	h2, err = h2.WithUpdatedConflictInfo(codec, &info)
   897  	require.NoError(t, err)
   898  
   899  	resolvesTo, partialResolvedH1, err =
   900  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2)
   901  	require.NoError(t, err)
   902  	require.True(t, resolvesTo)
   903  	require.Equal(t, h1, partialResolvedH1)
   904  
   905  	h2, err = ParseHandle(
   906  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   907  	require.NoError(t, err)
   908  	info = tlf.HandleExtension{
   909  		Date:   101,
   910  		Number: 51,
   911  		Type:   tlf.HandleExtensionFinalized,
   912  	}
   913  	h2.SetFinalizedInfo(&info)
   914  
   915  	resolvesTo, partialResolvedH1, err =
   916  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2)
   917  	require.NoError(t, err)
   918  	require.True(t, resolvesTo)
   919  	require.Equal(t, h1, partialResolvedH1)
   920  
   921  	// Test differing conflict info or finalized info.
   922  
   923  	h2, err = ParseHandle(
   924  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   925  	require.NoError(t, err)
   926  	info = tlf.HandleExtension{
   927  		Date:   100,
   928  		Number: 50,
   929  		Type:   tlf.HandleExtensionConflict,
   930  	}
   931  	h2, err = h2.WithUpdatedConflictInfo(codec, &info)
   932  	require.NoError(t, err)
   933  	info = tlf.HandleExtension{
   934  		Date:   99,
   935  		Number: 49,
   936  		Type:   tlf.HandleExtensionConflict,
   937  	}
   938  	h1, err = h1.WithUpdatedConflictInfo(codec, &info)
   939  	require.NoError(t, err)
   940  
   941  	resolvesTo, _, err =
   942  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2)
   943  	require.NoError(t, err)
   944  	require.False(t, resolvesTo)
   945  
   946  	h1, err = ParseHandle(
   947  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   948  	require.NoError(t, err)
   949  	h2, err = ParseHandle(
   950  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   951  	require.NoError(t, err)
   952  	info = tlf.HandleExtension{
   953  		Date:   101,
   954  		Number: 51,
   955  		Type:   tlf.HandleExtensionFinalized,
   956  	}
   957  	h2.SetFinalizedInfo(&info)
   958  	info = tlf.HandleExtension{
   959  		Date:   102,
   960  		Number: 52,
   961  		Type:   tlf.HandleExtensionFinalized,
   962  	}
   963  	h1.SetFinalizedInfo(&info)
   964  
   965  	resolvesTo, _, err =
   966  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2)
   967  	require.NoError(t, err)
   968  	require.False(t, resolvesTo)
   969  
   970  	// Try to add conflict info to a finalized handle.
   971  
   972  	h2, err = ParseHandle(
   973  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
   974  	require.NoError(t, err)
   975  	info = tlf.HandleExtension{
   976  		Date:   100,
   977  		Number: 50,
   978  		Type:   tlf.HandleExtensionConflict,
   979  	}
   980  	h2, err = h2.WithUpdatedConflictInfo(codec, &info)
   981  	require.NoError(t, err)
   982  
   983  	_, _, err =
   984  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2)
   985  	require.Error(t, err)
   986  
   987  	// Test positive resolution cases.
   988  
   989  	name1 = "u1,u2@twitter,u5#u3,u4@twitter"
   990  	h1, err = ParseHandle(
   991  		ctx, kbpki, ConstIDGetter{id}, nil, name1, tlf.Private)
   992  	require.NoError(t, err)
   993  
   994  	type testCase struct {
   995  		name2     string
   996  		resolveTo string
   997  	}
   998  
   999  	for _, tc := range []testCase{
  1000  		// Resolve to new user.
  1001  		{"u1,u2,u5#u3,u4@twitter", "u2"},
  1002  		// Resolve to existing writer.
  1003  		{"u1,u5#u3,u4@twitter", "u1"},
  1004  		// Resolve to existing reader.
  1005  		{"u1,u3,u5#u4@twitter", "u3"},
  1006  	} {
  1007  		h2, err = ParseHandle(
  1008  			ctx, kbpki, ConstIDGetter{id}, nil, tc.name2, tlf.Private)
  1009  		require.NoError(t, err)
  1010  
  1011  		daemon.AddNewAssertionForTestOrBust(tc.resolveTo, "u2@twitter")
  1012  
  1013  		resolvesTo, partialResolvedH1, err =
  1014  			h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h2)
  1015  		require.NoError(t, err)
  1016  		assert.True(t, resolvesTo, tc.name2)
  1017  		require.Equal(t, h2, partialResolvedH1, tc.name2)
  1018  
  1019  		daemon.RemoveAssertionForTest("u2@twitter")
  1020  	}
  1021  
  1022  	// Test negative resolution cases.
  1023  
  1024  	for _, tc := range []testCase{
  1025  		{"u1,u5#u3,u4@twitter", "u2"},
  1026  		{"u1,u2,u5#u3,u4@twitter", "u1"},
  1027  		{"u1,u2,u5#u3,u4@twitter", "u3"},
  1028  	} {
  1029  		h2, err = ParseHandle(
  1030  			ctx, kbpki, ConstIDGetter{id}, nil, tc.name2, tlf.Private)
  1031  		require.NoError(t, err)
  1032  
  1033  		daemon.AddNewAssertionForTestOrBust(tc.resolveTo, "u2@twitter")
  1034  
  1035  		resolvesTo, _, err =
  1036  			h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h2)
  1037  		require.NoError(t, err)
  1038  		assert.False(t, resolvesTo, tc.name2)
  1039  
  1040  		daemon.RemoveAssertionForTest("u2@twitter")
  1041  	}
  1042  }
  1043  
  1044  func TestHandleMigrationResolvesTo(t *testing.T) {
  1045  	ctx := context.Background()
  1046  
  1047  	localUsers := idutil.MakeLocalUsers(
  1048  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
  1049  	currentUID := localUsers[0].UID
  1050  	codec := kbfscodec.NewMsgpack()
  1051  	daemon := idutil.NewDaemonLocal(currentUID, localUsers, nil, codec)
  1052  
  1053  	kbpki := &idutiltest.DaemonKBPKI{
  1054  		Daemon: daemon,
  1055  	}
  1056  
  1057  	t.Log("Simple private team migration")
  1058  	id := tlf.FakeID(1, tlf.Private)
  1059  	// Handle without iteam.
  1060  	name1 := "u1,u2"
  1061  	h1, err := ParseHandle(
  1062  		ctx, kbpki, ConstIDGetter{id}, nil, name1, tlf.Private)
  1063  	require.NoError(t, err)
  1064  
  1065  	makeImplicitHandle := func(
  1066  		name string, ty tlf.Type, id tlf.ID) *Handle {
  1067  		wrName, suffix, err := tlf.SplitExtension(name)
  1068  		require.NoError(t, err)
  1069  		iteamInfo, err := daemon.ResolveIdentifyImplicitTeam(
  1070  			ctx, wrName, suffix, ty, true, "",
  1071  			keybase1.OfflineAvailability_NONE)
  1072  		require.NoError(t, err)
  1073  		err = daemon.CreateTeamTLF(ctx, iteamInfo.TID, id)
  1074  		require.NoError(t, err)
  1075  		h, err := ParseHandle(
  1076  			ctx, kbpki, ConstIDGetter{id}, nil, name, ty)
  1077  		require.NoError(t, err)
  1078  		require.Equal(t, tlf.TeamKeying, h.TypeForKeying())
  1079  		return h
  1080  	}
  1081  	h2 := makeImplicitHandle(name1, tlf.Private, id)
  1082  
  1083  	resolvesTo, partialResolvedH1, err :=
  1084  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h2)
  1085  	require.NoError(t, err)
  1086  	require.True(t, resolvesTo)
  1087  	require.Equal(t, h1, partialResolvedH1)
  1088  
  1089  	t.Log("Simple public team migration")
  1090  	idPub := tlf.FakeID(1, tlf.Public)
  1091  	// Handle without iteam.
  1092  	h1Pub, err := ParseHandle(
  1093  		ctx, kbpki, ConstIDGetter{idPub}, nil, name1, tlf.Public)
  1094  	require.NoError(t, err)
  1095  	h2Pub := makeImplicitHandle(name1, tlf.Public, idPub)
  1096  
  1097  	resolvesTo, partialResolvedH1, err =
  1098  		h1Pub.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{idPub}, nil, *h2Pub)
  1099  	require.NoError(t, err)
  1100  	require.True(t, resolvesTo)
  1101  	require.Equal(t, h1Pub, partialResolvedH1)
  1102  
  1103  	t.Log("Bad migration to team with extra user")
  1104  	name2 := "u1,u2,u3"
  1105  	h3 := makeImplicitHandle(name2, tlf.Private, id)
  1106  	resolvesTo, _, err =
  1107  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h3)
  1108  	require.NoError(t, err)
  1109  	require.False(t, resolvesTo)
  1110  
  1111  	t.Log("Bad migration to team with fewer users")
  1112  	name3 := "u1"
  1113  	h4 := makeImplicitHandle(name3, tlf.Private, id)
  1114  	resolvesTo, _, err =
  1115  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h4)
  1116  	require.NoError(t, err)
  1117  	require.False(t, resolvesTo)
  1118  
  1119  	t.Log("Bad migration to team with new readers")
  1120  	name4 := "u1,u2#u3"
  1121  	h5 := makeImplicitHandle(name4, tlf.Private, id)
  1122  	resolvesTo, _, err =
  1123  		h1.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h5)
  1124  	require.NoError(t, err)
  1125  	require.False(t, resolvesTo)
  1126  
  1127  	t.Log("Private team migration with unresolved users")
  1128  	// Handle without iteam.
  1129  	name5 := "u1,u2,u3@twitter"
  1130  	h6, err := ParseHandle(
  1131  		ctx, kbpki, ConstIDGetter{id}, nil, name5, tlf.Private)
  1132  	require.NoError(t, err)
  1133  	h7 := makeImplicitHandle(name5, tlf.Private, id)
  1134  	resolvesTo, partialResolvedH6, err :=
  1135  		h6.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h7)
  1136  	require.NoError(t, err)
  1137  	require.True(t, resolvesTo)
  1138  	require.Equal(t, h6, partialResolvedH6)
  1139  
  1140  	t.Log("Bad private team migration with extra unresolved user")
  1141  	// Handle without iteam.
  1142  	name6 := "u1,u2,u3@twitter,u4@twitter"
  1143  	h8 := makeImplicitHandle(name6, tlf.Private, id)
  1144  	resolvesTo, _, err =
  1145  		h6.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h8)
  1146  	require.NoError(t, err)
  1147  	require.False(t, resolvesTo)
  1148  
  1149  	t.Log("Private team migration with newly-resolved user")
  1150  	daemon.AddNewAssertionForTestOrBust("u3", "u3@twitter")
  1151  	resolvesTo, partialResolvedH6, err =
  1152  		h6.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h3)
  1153  	require.NoError(t, err)
  1154  	require.True(t, resolvesTo)
  1155  	require.Len(t, partialResolvedH6.UnresolvedWriters(), 0)
  1156  
  1157  	t.Log("Private team migration with conflict info")
  1158  	name7 := "u1,u2 (conflicted copy 2016-03-14 #3)"
  1159  	h9, err := ParseHandle(
  1160  		ctx, kbpki, ConstIDGetter{id}, nil, name7, tlf.Private)
  1161  	require.NoError(t, err)
  1162  	h10 := makeImplicitHandle(name7, tlf.Private, id)
  1163  	resolvesTo, partialResolvedH9, err :=
  1164  		h9.ResolvesTo(ctx, codec, kbpki, ConstIDGetter{id}, nil, *h10)
  1165  	require.NoError(t, err)
  1166  	require.True(t, resolvesTo)
  1167  	require.Equal(t, h9, partialResolvedH9)
  1168  }
  1169  
  1170  func TestParseHandleNoncanonicalExtensions(t *testing.T) {
  1171  	ctx := context.Background()
  1172  
  1173  	localUsers := idutil.MakeLocalUsers(
  1174  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
  1175  	currentUID := localUsers[0].UID
  1176  	daemon := idutil.NewDaemonLocal(
  1177  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
  1178  
  1179  	kbpki := &idutiltest.DaemonKBPKI{
  1180  		Daemon: daemon,
  1181  	}
  1182  
  1183  	name := "u1,u2#u3 (conflicted copy 2016-03-14 #3) (files before u2 account reset 2016-03-14 #2)"
  1184  	id := tlf.FakeID(1, tlf.Private)
  1185  	h, err := ParseHandle(
  1186  		ctx, kbpki, ConstIDGetter{id}, nil, name, tlf.Private)
  1187  	require.Nil(t, err)
  1188  	assert.Equal(t, tlf.HandleExtension{
  1189  		Type:   tlf.HandleExtensionConflict,
  1190  		Date:   tlf.HandleExtensionStaticTestDate,
  1191  		Number: 3,
  1192  	}, *h.ConflictInfo())
  1193  	assert.Equal(t, tlf.HandleExtension{
  1194  		Type:     tlf.HandleExtensionFinalized,
  1195  		Date:     tlf.HandleExtensionStaticTestDate,
  1196  		Number:   2,
  1197  		Username: "u2",
  1198  	}, *h.FinalizedInfo())
  1199  
  1200  	nonCanonicalName := "u1,u2#u3 (files before u2 account reset 2016-03-14 #2) (conflicted copy 2016-03-14 #3)"
  1201  	_, err = ParseHandle(
  1202  		ctx, kbpki, ConstIDGetter{id}, nil, nonCanonicalName, tlf.Private)
  1203  	assert.Equal(
  1204  		t, idutil.TlfNameNotCanonical{
  1205  			Name: nonCanonicalName, NameToTry: name}, errors.Cause(err))
  1206  }
  1207  
  1208  func TestParseHandleImplicitTeams(t *testing.T) {
  1209  	ctx := context.Background()
  1210  
  1211  	localUsers := idutil.MakeLocalUsers(
  1212  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
  1213  	currentUID := localUsers[0].UID
  1214  	daemon := idutil.NewDaemonLocal(
  1215  		currentUID, localUsers, nil, kbfscodec.NewMsgpack())
  1216  
  1217  	kbpki := &idutiltest.DaemonKBPKI{
  1218  		Daemon: daemon,
  1219  	}
  1220  
  1221  	counter := byte(1)
  1222  	newITeam := func(name, suffix string, ty tlf.Type) (
  1223  		keybase1.TeamID, tlf.ID) {
  1224  		iteamInfo, err := daemon.ResolveIdentifyImplicitTeam(
  1225  			ctx, name, suffix, ty, true, "", keybase1.OfflineAvailability_NONE)
  1226  		require.NoError(t, err)
  1227  		tlfID := tlf.FakeID(counter, ty)
  1228  		counter++
  1229  		err = daemon.CreateTeamTLF(ctx, iteamInfo.TID, tlfID)
  1230  		require.NoError(t, err)
  1231  		return iteamInfo.TID, tlfID
  1232  	}
  1233  
  1234  	check := func(name string, tid keybase1.TeamID, tlfID tlf.ID, ty tlf.Type) {
  1235  		h, err := ParseHandle(ctx, kbpki, nil, nil, name, ty)
  1236  		require.NoError(t, err)
  1237  		require.Len(t, h.ResolvedWriters(), 1)
  1238  		require.Len(t, h.ResolvedReaders(), 0)
  1239  		require.Len(t, h.UnresolvedWriters(), 0)
  1240  		require.Len(t, h.UnresolvedReaders(), 0)
  1241  		require.Equal(t, tid.String(), h.FirstResolvedWriter().String())
  1242  		require.Equal(t, tlfID, h.tlfID)
  1243  	}
  1244  
  1245  	t.Log("Private implicit teams")
  1246  	tid1, tlfID1 := newITeam("u1", "", tlf.Private)
  1247  	check("u1", tid1, tlfID1, tlf.Private)
  1248  	tid2, tlfID2 := newITeam("u1,u2", "", tlf.Private)
  1249  	check("u1,u2", tid2, tlfID2, tlf.Private)
  1250  	tid3, tlfID3 := newITeam("u1,u2,u3", "", tlf.Private)
  1251  	check("u1,u2,u3", tid3, tlfID3, tlf.Private)
  1252  
  1253  	t.Log("Public implicit teams")
  1254  	tid4, tlfID4 := newITeam("u1", "", tlf.Public)
  1255  	check("u1", tid4, tlfID4, tlf.Public)
  1256  	tid5, tlfID5 := newITeam("u1,u2", "", tlf.Public)
  1257  	check("u1,u2", tid5, tlfID5, tlf.Public)
  1258  
  1259  	t.Log("Implicit team with a suffix")
  1260  	tid6, tlfID6 := newITeam(
  1261  		"u1,u2", "(conflicted copy 2016-03-14 #3)", tlf.Private)
  1262  	check("u1,u2 (conflicted copy 2016-03-14 #3)", tid6, tlfID6, tlf.Private)
  1263  
  1264  	t.Log("Implicit team with readers")
  1265  	tid7, tlfID7 := newITeam("u1,u2#u3", "", tlf.Private)
  1266  	check("u1,u2#u3", tid7, tlfID7, tlf.Private)
  1267  }
  1268  
  1269  type offlineResolveCounterKBPKI struct {
  1270  	idutil.KBPKI
  1271  
  1272  	lock                    sync.Mutex
  1273  	bestEffortOfflineCounts map[string]int
  1274  }
  1275  
  1276  func (d *offlineResolveCounterKBPKI) countBestEffort(
  1277  	offline keybase1.OfflineAvailability, s string) {
  1278  	if offline != keybase1.OfflineAvailability_BEST_EFFORT {
  1279  		return
  1280  	}
  1281  	d.lock.Lock()
  1282  	d.bestEffortOfflineCounts[s]++
  1283  	d.lock.Unlock()
  1284  }
  1285  
  1286  func (d *offlineResolveCounterKBPKI) Resolve(
  1287  	ctx context.Context, assertion string,
  1288  	offline keybase1.OfflineAvailability) (
  1289  	kbname.NormalizedUsername, keybase1.UserOrTeamID, error) {
  1290  	d.countBestEffort(offline, assertion)
  1291  	return d.KBPKI.Resolve(ctx, assertion, offline)
  1292  }
  1293  
  1294  func (d *offlineResolveCounterKBPKI) ResolveTeamTLFID(
  1295  	ctx context.Context, teamID keybase1.TeamID,
  1296  	offline keybase1.OfflineAvailability) (tlf.ID, error) {
  1297  	d.countBestEffort(offline, teamID.String())
  1298  	return d.KBPKI.ResolveTeamTLFID(ctx, teamID, offline)
  1299  }
  1300  
  1301  func (d *offlineResolveCounterKBPKI) ResolveImplicitTeam(
  1302  	ctx context.Context, assertions, suffix string, tlfType tlf.Type,
  1303  	offline keybase1.OfflineAvailability) (idutil.ImplicitTeamInfo, error) {
  1304  	d.countBestEffort(offline, "iteam:"+assertions+" "+suffix)
  1305  	return d.KBPKI.ResolveImplicitTeam(
  1306  		ctx, assertions, suffix, tlfType, offline)
  1307  }
  1308  
  1309  type testOfflineStatusPathsGetter struct {
  1310  	bestEffortPaths map[string]bool
  1311  }
  1312  
  1313  func (t *testOfflineStatusPathsGetter) OfflineAvailabilityForPath(
  1314  	tlfPath string) keybase1.OfflineAvailability {
  1315  	if t.bestEffortPaths[tlfPath] {
  1316  		return keybase1.OfflineAvailability_BEST_EFFORT
  1317  	}
  1318  	return keybase1.OfflineAvailability_NONE
  1319  }
  1320  
  1321  func (t *testOfflineStatusPathsGetter) OfflineAvailabilityForID(
  1322  	tlfID tlf.ID) keybase1.OfflineAvailability {
  1323  	panic("Not supported")
  1324  }
  1325  
  1326  func TestParseHandleOfflineAvailability(t *testing.T) {
  1327  	ctx := context.Background()
  1328  
  1329  	localUsers := idutil.MakeLocalUsers(
  1330  		[]kbname.NormalizedUsername{"u1", "u2", "u3"})
  1331  	localUsers[0].Asserts = []string{"u1@twitter"}
  1332  	currentUID := localUsers[0].UID
  1333  	localTeams := idutil.MakeLocalTeams(
  1334  		[]kbname.NormalizedUsername{"u1u2u3", "u3u2u1"})
  1335  	daemon := idutil.NewDaemonLocal(
  1336  		currentUID, localUsers, localTeams, kbfscodec.NewMsgpack())
  1337  
  1338  	kbpki := &offlineResolveCounterKBPKI{
  1339  		KBPKI: &idutiltest.DaemonKBPKI{
  1340  			Daemon: daemon,
  1341  		},
  1342  		bestEffortOfflineCounts: make(map[string]int),
  1343  	}
  1344  
  1345  	osg := &testOfflineStatusPathsGetter{make(map[string]bool)}
  1346  	osg.bestEffortPaths["/keybase/private/u2"] = true
  1347  
  1348  	t.Log("Check unsynced private TLF")
  1349  	_, err := ParseHandle(ctx, kbpki, nil, osg, "u1", tlf.Private)
  1350  	require.NoError(t, err)
  1351  	require.Equal(t, kbpki.bestEffortOfflineCounts["u1"], 0)
  1352  
  1353  	t.Log("Check synced private TLF")
  1354  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u2", tlf.Private)
  1355  	require.NoError(t, err)
  1356  	require.Equal(t, kbpki.bestEffortOfflineCounts["u2"], 1)
  1357  
  1358  	t.Log("Check synced private shared TLF")
  1359  	osg.bestEffortPaths["/keybase/private/u1,u2,u3"] = true
  1360  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u1,u2,u3", tlf.Private)
  1361  	require.NoError(t, err)
  1362  	require.Equal(t, 1, kbpki.bestEffortOfflineCounts["u1"])
  1363  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u2"])
  1364  	require.Equal(t, 1, kbpki.bestEffortOfflineCounts["u3"])
  1365  
  1366  	t.Log("Check synced private shared TLF, different order")
  1367  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u3,u1,u2", tlf.Private)
  1368  	assert.Equal(
  1369  		t, idutil.TlfNameNotCanonical{Name: "u3,u1,u2", NameToTry: "u1,u2,u3"},
  1370  		errors.Cause(err))
  1371  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u1"])
  1372  	require.Equal(t, 3, kbpki.bestEffortOfflineCounts["u2"])
  1373  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u3"])
  1374  
  1375  	t.Log("Check synced private shared TLF, " +
  1376  		"resolved assertions don't use best effort.")
  1377  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u1@twitter,u2,u3",
  1378  		tlf.Private)
  1379  	assert.Equal(
  1380  		t, idutil.TlfNameNotCanonical{
  1381  			Name: "u1@twitter,u2,u3", NameToTry: "u1,u2,u3"},
  1382  		errors.Cause(err))
  1383  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u1"])
  1384  	require.Equal(t, 0, kbpki.bestEffortOfflineCounts["u1@twitter"])
  1385  	require.Equal(t, 3, kbpki.bestEffortOfflineCounts["u2"])
  1386  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u3"])
  1387  
  1388  	t.Log("Check synced private shared TLF, " +
  1389  		"unresolved assertions do use best effort.")
  1390  	osg.bestEffortPaths["/keybase/private/u1,u2@twitter,u3"] = true
  1391  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u1,u2@twitter,u3",
  1392  		tlf.Private)
  1393  	assert.NoError(t, err)
  1394  	require.Equal(t, 3, kbpki.bestEffortOfflineCounts["u1"])
  1395  	require.Equal(t, 1, kbpki.bestEffortOfflineCounts["u2@twitter"])
  1396  	require.Equal(t, 3, kbpki.bestEffortOfflineCounts["u2"])
  1397  	require.Equal(t, 3, kbpki.bestEffortOfflineCounts["u3"])
  1398  
  1399  	t.Log("Check synced private shared TLF, with readers")
  1400  	osg.bestEffortPaths["/keybase/private/u1#u2,u3"] = true
  1401  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u1#u2,u3",
  1402  		tlf.Private)
  1403  	assert.NoError(t, err)
  1404  	require.Equal(t, 4, kbpki.bestEffortOfflineCounts["u1"])
  1405  	require.Equal(t, 4, kbpki.bestEffortOfflineCounts["u2"])
  1406  	require.Equal(t, 4, kbpki.bestEffortOfflineCounts["u3"])
  1407  
  1408  	t.Log("Check synced private shared TLF, with readers, different order")
  1409  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u1#u3,u2",
  1410  		tlf.Private)
  1411  	assert.Equal(
  1412  		t, idutil.TlfNameNotCanonical{Name: "u1#u3,u2", NameToTry: "u1#u2,u3"},
  1413  		errors.Cause(err))
  1414  	require.Equal(t, 5, kbpki.bestEffortOfflineCounts["u1"])
  1415  	require.Equal(t, 5, kbpki.bestEffortOfflineCounts["u2"])
  1416  	require.Equal(t, 5, kbpki.bestEffortOfflineCounts["u3"])
  1417  
  1418  	t.Log("Check synced private shared TLF, with extension")
  1419  	ext := "(conflicted copy 2016-03-14 #3)"
  1420  	osg.bestEffortPaths["/keybase/private/u1,u2 "+ext] = true
  1421  	_, err = ParseHandle(
  1422  		ctx, kbpki, nil, osg, "u1,u2 "+ext, tlf.Private)
  1423  	assert.NoError(t, err)
  1424  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u1"])
  1425  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u2"])
  1426  	require.Equal(t, 5, kbpki.bestEffortOfflineCounts["u3"])
  1427  
  1428  	t.Log("Check synced private shared TLF, with extension, different order, " +
  1429  		"with reader and unresolved assertion")
  1430  	osg.bestEffortPaths["/keybase/private/u1,u3#u2@twitter "+ext] = true
  1431  	_, err = ParseHandle(
  1432  		ctx, kbpki, nil, osg, "u3,u1#u2@twitter "+ext, tlf.Private)
  1433  	assert.Equal(
  1434  		t, idutil.TlfNameNotCanonical{
  1435  			Name:      "u3,u1#u2@twitter " + ext,
  1436  			NameToTry: "u1,u3#u2@twitter " + ext,
  1437  		}, errors.Cause(err))
  1438  	require.Equal(t, 7, kbpki.bestEffortOfflineCounts["u1"])
  1439  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u2@twitter"])
  1440  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u2"])
  1441  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u3"])
  1442  
  1443  	t.Log("Check synced team TLF")
  1444  	osg.bestEffortPaths["/keybase/team/u1u2u3"] = true
  1445  	tlfID1 := tlf.FakeID(1, tlf.SingleTeam)
  1446  	err = daemon.CreateTeamTLF(ctx, localTeams[0].TID, tlfID1)
  1447  	require.NoError(t, err)
  1448  	_, err = ParseHandle(
  1449  		ctx, kbpki, ConstIDGetter{tlfID1}, osg, "u1u2u3", tlf.SingleTeam)
  1450  	assert.NoError(t, err)
  1451  	require.Equal(t, 1, kbpki.bestEffortOfflineCounts["team:u1u2u3"])
  1452  	require.Equal(t, 7, kbpki.bestEffortOfflineCounts["u1"])
  1453  	require.Equal(t, 2, kbpki.bestEffortOfflineCounts["u2@twitter"])
  1454  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u2"])
  1455  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u3"])
  1456  	require.Equal(
  1457  		t, 1, kbpki.bestEffortOfflineCounts[localTeams[0].TID.String()])
  1458  
  1459  	t.Log("Check unsynced team TLF")
  1460  	tlfID2 := tlf.FakeID(2, tlf.SingleTeam)
  1461  	err = daemon.CreateTeamTLF(ctx, localTeams[1].TID, tlfID2)
  1462  	require.NoError(t, err)
  1463  	_, err = ParseHandle(
  1464  		ctx, kbpki, ConstIDGetter{tlfID2}, osg, "u3u2u1", tlf.SingleTeam)
  1465  	assert.NoError(t, err)
  1466  	require.Equal(t, 1, kbpki.bestEffortOfflineCounts["team:u1u2u3"])
  1467  	require.Equal(t, 0, kbpki.bestEffortOfflineCounts["team:u3u2u1"])
  1468  
  1469  	t.Log("Check implicit team TLF")
  1470  	info, err := daemon.ResolveIdentifyImplicitTeam(
  1471  		ctx, "u1,u2,u3", "", tlf.Private, true, "",
  1472  		keybase1.OfflineAvailability_BEST_EFFORT)
  1473  	require.NoError(t, err)
  1474  	tlfID3 := tlf.FakeID(3, tlf.Private)
  1475  	err = daemon.CreateTeamTLF(ctx, info.TID, tlfID3)
  1476  	require.NoError(t, err)
  1477  	_, err = ParseHandle(ctx, kbpki, nil, osg, "u1,u2,u3", tlf.Private)
  1478  	require.NoError(t, err)
  1479  	// The iteam has a best-effort count of 3, because the earlier
  1480  	// lookup of 'u1,u2,u3' and 'u3,u1,u2' already tried to find an
  1481  	// implicit team once with best-effort, but there wasn't yet a TLF
  1482  	// ID associated with the implicit team.  The per-user counts
  1483  	// won't change now that the existence of the TLF ID for the iteam
  1484  	// short-circuits those lookups.
  1485  	require.Equal(t, 3, kbpki.bestEffortOfflineCounts["iteam:u1,u2,u3 "])
  1486  	require.Equal(t, 7, kbpki.bestEffortOfflineCounts["u1"])
  1487  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u2"])
  1488  	require.Equal(t, 6, kbpki.bestEffortOfflineCounts["u3"])
  1489  }