github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/systests/rpc_test.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package systests
     5  
     6  // Test various RPCs that are used mainly in other clients but not by the CLI.
     7  
     8  import (
     9  	"fmt"
    10  	"regexp"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"testing"
    15  
    16  	"github.com/davecgh/go-spew/spew"
    17  	"github.com/keybase/client/go/client"
    18  	"github.com/keybase/client/go/gregor"
    19  	"github.com/keybase/client/go/libkb"
    20  	"github.com/keybase/client/go/protocol/gregor1"
    21  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    22  	service "github.com/keybase/client/go/service"
    23  	"github.com/keybase/client/go/teams"
    24  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    25  	"github.com/stretchr/testify/require"
    26  	context "golang.org/x/net/context"
    27  )
    28  
    29  type fakeConnectivityMonitor struct {
    30  	sync.Mutex
    31  	res libkb.ConnectivityMonitorResult
    32  }
    33  
    34  func (f *fakeConnectivityMonitor) IsConnected(ctx context.Context) libkb.ConnectivityMonitorResult {
    35  	f.Lock()
    36  	defer f.Unlock()
    37  	return f.res
    38  }
    39  
    40  func (f *fakeConnectivityMonitor) Set(r libkb.ConnectivityMonitorResult) {
    41  	f.Lock()
    42  	defer f.Unlock()
    43  	f.res = r
    44  }
    45  
    46  func (f *fakeConnectivityMonitor) CheckReachability(ctx context.Context) error {
    47  	return nil
    48  }
    49  
    50  func TestRPCs(t *testing.T) {
    51  	tc := setupTest(t, "rpcs")
    52  	defer tc.Cleanup()
    53  
    54  	tc2 := cloneContext(tc)
    55  	defer tc2.Cleanup()
    56  
    57  	// Set a connectivity manager we can control, and set NoGregor so that
    58  	// way it's not overwritten when we startup gregor.
    59  	fcm := fakeConnectivityMonitor{}
    60  	fcm.Set(libkb.ConnectivityMonitorYes)
    61  	tc.G.ConnectivityMonitor = &fcm
    62  	tc.G.Env.Test.NoGregor = true
    63  
    64  	stopCh := make(chan error)
    65  	svc := service.NewService(tc.G, false)
    66  	startCh := svc.GetStartChannel()
    67  	go func() {
    68  		err := svc.Run()
    69  		if err != nil {
    70  			t.Logf("Running the service produced an error: %v", err)
    71  		}
    72  		stopCh <- err
    73  	}()
    74  
    75  	<-startCh
    76  
    77  	// Add test RPC methods here.
    78  	stage := func(label string) {
    79  		t.Logf("> Stage: %v", label)
    80  	}
    81  	stage("testIdentifyResolve3")
    82  	testIdentifyResolve3(t, tc2.G)
    83  	stage("testCheckInvitationCode")
    84  	testCheckInvitationCode(t, tc2.G)
    85  	stage("testLoadAllPublicKeysUnverified")
    86  	testLoadAllPublicKeysUnverified(t, tc2.G)
    87  	stage("testLoadUserWithNoKeys")
    88  	testLoadUserWithNoKeys(t, tc2.G)
    89  	stage("test LoadUserPlusKeysV2")
    90  	testLoadUserPlusKeysV2(t, tc2.G)
    91  	stage("testCheckDevicesForUser")
    92  	testCheckDevicesForUser(t, tc2.G)
    93  	stage("testIdentify2")
    94  	testIdentify2(t, tc2.G)
    95  	stage("testMerkle")
    96  	testMerkle(t, tc2.G)
    97  	stage("testConfig")
    98  	testConfig(t, tc2.G)
    99  	stage("testResolve3Offline")
   100  	testResolve3Offline(t, tc2.G, &fcm)
   101  	stage("testLoadUserPlusKeysV2Offline")
   102  	testLoadUserPlusKeysV2Offline(t, tc2.G, &fcm)
   103  	stage("testGetUpdateInfo2")
   104  	testGetUpdateInfo2(t, tc2.G)
   105  
   106  	if err := CtlStop(tc2.G); err != nil {
   107  		t.Fatal(err)
   108  	}
   109  
   110  	// If the server failed, it's also an error
   111  	if err := <-stopCh; err != nil {
   112  		t.Fatal(err)
   113  	}
   114  }
   115  
   116  func testResolve3Offline(t *testing.T, g *libkb.GlobalContext, fcm *fakeConnectivityMonitor) {
   117  	cli, err := client.GetIdentifyClient(g)
   118  	require.NoError(t, err)
   119  	fetch := func() {
   120  		arg := keybase1.Resolve3Arg{Assertion: "uid:eb72f49f2dde6429e5d78003dae0c919", Oa: keybase1.OfflineAvailability_BEST_EFFORT}
   121  		res, err := cli.Resolve3(context.TODO(), arg)
   122  		require.NoError(t, err)
   123  		require.Equal(t, "t_tracy", res.Name)
   124  	}
   125  	fetchFail := func(expectedError error) {
   126  		arg := keybase1.Resolve3Arg{Assertion: "no_such_user_yo", Oa: keybase1.OfflineAvailability_BEST_EFFORT}
   127  		_, err = cli.Resolve3(context.TODO(), arg)
   128  		require.Error(t, err)
   129  		require.IsType(t, expectedError, err)
   130  	}
   131  
   132  	fetch()
   133  	fcm.Set(libkb.ConnectivityMonitorNo)
   134  	fetch()
   135  	fetchFail(libkb.OfflineError{})
   136  	fcm.Set(libkb.ConnectivityMonitorYes)
   137  	fetch()
   138  	fetchFail(libkb.NotFoundError{})
   139  }
   140  
   141  func testIdentifyResolve3(t *testing.T, g *libkb.GlobalContext) {
   142  
   143  	cli, err := client.GetIdentifyClient(g)
   144  	if err != nil {
   145  		t.Fatalf("failed to get new identifyclient: %v", err)
   146  	}
   147  
   148  	// We don't want to hit the cache, since the previous lookup never hit the
   149  	// server.  For Resolve3, we have to, since we need a username.  So test that
   150  	// here.
   151  	if res, err := cli.Resolve3(context.TODO(), keybase1.Resolve3Arg{Assertion: "uid:eb72f49f2dde6429e5d78003dae0c919"}); err != nil {
   152  		t.Fatalf("Resolve failed: %v\n", err)
   153  	} else if res.Name != "t_tracy" {
   154  		t.Fatalf("Wrong username: %s != 't_tracy", res.Name)
   155  	}
   156  
   157  	if res, err := cli.Resolve3(context.TODO(), keybase1.Resolve3Arg{Assertion: "t_tracy@rooter"}); err != nil {
   158  		t.Fatalf("Resolve3 failed: %v\n", err)
   159  	} else if res.Name != "t_tracy" {
   160  		t.Fatalf("Wrong name: %s != 't_tracy", res.Name)
   161  	} else if !res.Id.AsUserOrBust().Equal(keybase1.UID("eb72f49f2dde6429e5d78003dae0c919")) {
   162  		t.Fatalf("Wrong uid for tracy: %s\n", res.Id)
   163  	}
   164  
   165  	if _, err := cli.Resolve3(context.TODO(), keybase1.Resolve3Arg{Assertion: "foobag@rooter"}); err == nil {
   166  		t.Fatalf("expected an error on a bad resolve, but got none")
   167  	} else if _, ok := err.(libkb.ResolutionError); !ok {
   168  		t.Fatalf("Wrong error: wanted type %T but got (%v, %T)", libkb.ResolutionError{}, err, err)
   169  	}
   170  
   171  	if res, err := cli.Resolve3(context.TODO(), keybase1.Resolve3Arg{Assertion: "t_tracy"}); err != nil {
   172  		t.Fatalf("Resolve3 failed: %v\n", err)
   173  	} else if res.Name != "t_tracy" {
   174  		t.Fatalf("Wrong name: %s != 't_tracy", res.Name)
   175  	} else if !res.Id.AsUserOrBust().Equal(keybase1.UID("eb72f49f2dde6429e5d78003dae0c919")) {
   176  		t.Fatalf("Wrong uid for tracy: %s\n", res.Id)
   177  	}
   178  }
   179  
   180  func testCheckInvitationCode(t *testing.T, g *libkb.GlobalContext) {
   181  	cli, err := client.GetSignupClient(g)
   182  	if err != nil {
   183  		t.Fatalf("failed to get a signup client: %v", err)
   184  	}
   185  
   186  	err = cli.CheckInvitationCode(context.TODO(), keybase1.CheckInvitationCodeArg{InvitationCode: libkb.TestInvitationCode})
   187  	if err != nil {
   188  		t.Fatalf("Did not expect an error code, but got: %v", err)
   189  	}
   190  	err = cli.CheckInvitationCode(context.TODO(), keybase1.CheckInvitationCodeArg{InvitationCode: "eeoeoeoe333o3"})
   191  	if _, ok := err.(libkb.BadInvitationCodeError); !ok {
   192  		t.Fatalf("Expected an error code, but got %T %v", err, err)
   193  	}
   194  }
   195  
   196  func testLoadAllPublicKeysUnverified(t *testing.T, g *libkb.GlobalContext) {
   197  
   198  	cli, err := client.GetUserClient(g)
   199  	if err != nil {
   200  		t.Fatalf("failed to get user client: %s", err)
   201  	}
   202  
   203  	// t_rosetta
   204  	arg := keybase1.LoadAllPublicKeysUnverifiedArg{Uid: keybase1.UID("b8939251cb3d367e68587acb33a64d19")}
   205  	res, err := cli.LoadAllPublicKeysUnverified(context.TODO(), arg)
   206  	if err != nil {
   207  		t.Fatalf("failed to make load keys call: %s", err)
   208  	}
   209  
   210  	if len(res) != 3 {
   211  		t.Fatalf("wrong amount of keys loaded: %d != %d", len(res), 3)
   212  	}
   213  
   214  	keys := map[keybase1.KID]bool{
   215  		keybase1.KID("0101fe1183765f256289427d6943cd8bab3b5fe095bcdd27f031ed298da523efd3120a"): true,
   216  		keybase1.KID("0101b5839c4ccaa9d03b3016b9aa73a7e3eafb067f9c86c07a6f2f79cb8558b1c97f0a"): true,
   217  		keybase1.KID("0101188ee7e63ccbd05af498772ab2975ee29df773240d17dde09aecf6c132a5a9a60a"): true,
   218  	}
   219  
   220  	for _, key := range res {
   221  		if _, ok := keys[key.KID]; !ok {
   222  			t.Fatalf("unknown key in response: %s", key.KID)
   223  		}
   224  	}
   225  }
   226  
   227  func testLoadUserPlusKeysV2Offline(t *testing.T, g *libkb.GlobalContext, fcm *fakeConnectivityMonitor) {
   228  	cli, err := client.GetUserClient(g)
   229  	require.NoError(t, err)
   230  
   231  	kid := keybase1.KID("012012a40a6b77a9de5e48922262870565900f5689e179761ea8c8debaa586bfd0090a")
   232  	uid := keybase1.UID("359c7644857203be38bfd3bf79bf1819")
   233  
   234  	fetch := func() {
   235  		arg := keybase1.LoadUserPlusKeysV2Arg{
   236  			Uid:        uid,
   237  			PollForKID: kid,
   238  			Oa:         keybase1.OfflineAvailability_BEST_EFFORT,
   239  		}
   240  		frank, err := cli.LoadUserPlusKeysV2(context.TODO(), arg)
   241  		require.NoError(t, err)
   242  		require.NotNil(t, frank)
   243  		require.Equal(t, len(frank.PastIncarnations), 0)
   244  		require.Equal(t, frank.Current.Username, "t_frank")
   245  		_, found := frank.Current.DeviceKeys[kid]
   246  		require.True(t, found)
   247  		require.Nil(t, frank.Current.Reset)
   248  	}
   249  	fetchFail := func(expectedError error) {
   250  		arg := keybase1.LoadUserPlusKeysV2Arg{
   251  			Uid: "00000000000000000000000000000000",
   252  			Oa:  keybase1.OfflineAvailability_BEST_EFFORT,
   253  		}
   254  		_, err := cli.LoadUserPlusKeysV2(context.TODO(), arg)
   255  		require.Error(t, err)
   256  		require.IsType(t, expectedError, err)
   257  	}
   258  
   259  	fetch()
   260  	fcm.Set(libkb.ConnectivityMonitorNo)
   261  	fetch()
   262  	fetchFail(libkb.OfflineError{})
   263  	fcm.Set(libkb.ConnectivityMonitorYes)
   264  	fetch()
   265  	fetchFail(libkb.NotFoundError{})
   266  }
   267  
   268  func testLoadUserPlusKeysV2(t *testing.T, g *libkb.GlobalContext) {
   269  	cli, err := client.GetUserClient(g)
   270  	if err != nil {
   271  		t.Fatalf("failed to get a user client: %v", err)
   272  	}
   273  
   274  	kid := keybase1.KID("012012a40a6b77a9de5e48922262870565900f5689e179761ea8c8debaa586bfd0090a")
   275  	uid := keybase1.UID("359c7644857203be38bfd3bf79bf1819")
   276  
   277  	frank, err := cli.LoadUserPlusKeysV2(context.TODO(), keybase1.LoadUserPlusKeysV2Arg{Uid: uid, PollForKID: kid})
   278  	require.NoError(t, err)
   279  	require.NotNil(t, frank)
   280  	require.Equal(t, len(frank.PastIncarnations), 0)
   281  	require.Equal(t, frank.Current.Username, "t_frank")
   282  	_, found := frank.Current.DeviceKeys[kid]
   283  	require.True(t, found)
   284  	require.Nil(t, frank.Current.Reset)
   285  }
   286  
   287  func testLoadUserWithNoKeys(t *testing.T, g *libkb.GlobalContext) {
   288  	// The LoadUser class in libkb returns an error by default if the user in
   289  	// question has no keys. The RPC methods that wrap it should suppress this
   290  	// error, by setting the PublicKeyOptional flag.
   291  
   292  	cli, err := client.GetUserClient(g)
   293  	if err != nil {
   294  		t.Fatalf("failed to get a user client: %v", err)
   295  	}
   296  
   297  	// Check the LoadUserByName RPC. t_ellen is a test user with no keys.
   298  	loadUserByNameArg := keybase1.LoadUserByNameArg{Username: "t_ellen"}
   299  	tEllen, err := cli.LoadUserByName(context.TODO(), loadUserByNameArg)
   300  	if err != nil {
   301  		t.Fatal(err)
   302  	}
   303  	if tEllen.Username != "t_ellen" {
   304  		t.Fatalf("expected t_ellen, saw %s", tEllen.Username)
   305  	}
   306  
   307  	// Check the LoadUser RPC.
   308  	loadUserArg := keybase1.LoadUserArg{Uid: tEllen.Uid}
   309  	tEllen2, err := cli.LoadUser(context.TODO(), loadUserArg)
   310  	if err != nil {
   311  		t.Fatal(err)
   312  	}
   313  	if tEllen2.Username != "t_ellen" {
   314  		t.Fatalf("expected t_ellen, saw %s", tEllen2.Username)
   315  	}
   316  }
   317  
   318  func testCheckDevicesForUser(t *testing.T, g *libkb.GlobalContext) {
   319  	cli, err := client.GetDeviceClient(g)
   320  	if err != nil {
   321  		t.Fatalf("failed to get a device client: %v", err)
   322  	}
   323  	err = cli.CheckDeviceNameForUser(context.TODO(), keybase1.CheckDeviceNameForUserArg{
   324  		Username:   "t_frank",
   325  		Devicename: "bad $ device $ name",
   326  	})
   327  	if _, ok := err.(libkb.DeviceBadNameError); !ok {
   328  		t.Fatalf("wanted a bad device name error; got %v", err)
   329  	}
   330  	err = cli.CheckDeviceNameForUser(context.TODO(), keybase1.CheckDeviceNameForUserArg{
   331  		Username:   "t_frank",
   332  		Devicename: "go c lient",
   333  	})
   334  	if _, ok := err.(libkb.DeviceNameInUseError); !ok {
   335  		t.Fatalf("wanted a name in use error; got %v", err)
   336  	}
   337  }
   338  
   339  func testIdentify2(t *testing.T, g *libkb.GlobalContext) {
   340  
   341  	cli, err := client.GetIdentifyClient(g)
   342  	if err != nil {
   343  		t.Fatalf("failed to get new identifyclient: %v", err)
   344  	}
   345  
   346  	_, err = cli.Identify2(context.TODO(), keybase1.Identify2Arg{
   347  		UserAssertion:    "t_alice",
   348  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI,
   349  	})
   350  	if err != nil {
   351  		t.Fatalf("Identify2 failed: %v\n", err)
   352  	}
   353  
   354  	_, err = cli.Identify2(context.TODO(), keybase1.Identify2Arg{
   355  		UserAssertion:    "t_weriojweroi",
   356  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_GUI,
   357  	})
   358  	if _, ok := err.(libkb.NotFoundError); !ok {
   359  		t.Fatalf("Expected a not-found error, but got: %v (%T)", err, err)
   360  	}
   361  }
   362  
   363  func testMerkle(t *testing.T, g *libkb.GlobalContext) {
   364  
   365  	cli, err := client.GetMerkleClient(g)
   366  	if err != nil {
   367  		t.Fatalf("failed to get new merkle client: %v", err)
   368  	}
   369  
   370  	root, err := cli.GetCurrentMerkleRoot(context.TODO(), int(-1))
   371  	if err != nil {
   372  		t.Fatalf("GetCurrentMerkleRoot failed: %v\n", err)
   373  	}
   374  	if root.Root.Seqno <= keybase1.Seqno(0) {
   375  		t.Fatalf("Failed basic sanity check")
   376  	}
   377  }
   378  
   379  func testConfig(t *testing.T, g *libkb.GlobalContext) {
   380  
   381  	cli, err := client.GetConfigClient(g)
   382  	if err != nil {
   383  		t.Fatalf("failed to get new config client: %v", err)
   384  	}
   385  	config, err := cli.GetConfig(context.TODO(), 0)
   386  	if err != nil {
   387  		t.Fatal(err)
   388  	}
   389  	if config.ServerURI == "" {
   390  		t.Fatal("No service URI")
   391  	}
   392  }
   393  
   394  func testGetUpdateInfo2(t *testing.T, g *libkb.GlobalContext) {
   395  	cli, err := client.GetConfigClient(g)
   396  	require.NoError(t, err)
   397  	res, err := cli.GetUpdateInfo2(context.TODO(), keybase1.GetUpdateInfo2Arg{})
   398  	require.NoError(t, err)
   399  	status, err := res.Status()
   400  	require.NoError(t, err)
   401  	require.Equal(t, keybase1.UpdateInfoStatus2_OK, status)
   402  	platform := "ios"
   403  	version := "0.0.1"
   404  	res, err = cli.GetUpdateInfo2(context.TODO(), keybase1.GetUpdateInfo2Arg{Platform: &platform, Version: &version})
   405  	require.NoError(t, err)
   406  	status, err = res.Status()
   407  	require.NoError(t, err)
   408  	require.Equal(t, keybase1.UpdateInfoStatus2_CRITICAL, status)
   409  	require.IsType(t, "foo", res.Critical().Message)
   410  	require.True(t, len(res.Critical().Message) > 10)
   411  }
   412  
   413  type FakeGregorState struct {
   414  	items []gregor.Item
   415  }
   416  
   417  func (s FakeGregorState) Items() ([]gregor.Item, error) {
   418  	return s.items, nil
   419  }
   420  func (s FakeGregorState) GetItem(msgID gregor.MsgID) (i gregor.Item, r bool) { return }
   421  func (s FakeGregorState) ItemsInCategory(c gregor.Category) (i []gregor.Item, e error) {
   422  	return
   423  }
   424  func (s FakeGregorState) ItemsWithCategoryPrefix(c gregor.Category) (i []gregor.Item, r error) {
   425  	return
   426  }
   427  func (s FakeGregorState) Marshal() (b []byte, e error)              { return }
   428  func (s FakeGregorState) Hash() (b []byte, e error)                 { return }
   429  func (s FakeGregorState) Export() (p gregor.ProtocolState, e error) { return }
   430  
   431  func buildGregorItem(category, deviceID, msgID string) gregor.Item {
   432  	imd := gregor1.ItemAndMetadata{
   433  		Md_: &gregor1.Metadata{
   434  			MsgID_: gregor1.MsgID([]byte(msgID)),
   435  		},
   436  		Item_: &gregor1.Item{
   437  			Category_: gregor1.Category(category),
   438  			Body_:     gregor1.Body(fmt.Sprintf(`{"device_id": "%s"}`, deviceID)),
   439  		},
   440  	}
   441  	return gregor.Item(imd)
   442  }
   443  
   444  func TestDismissDeviceChangeNotifications(t *testing.T) {
   445  	tc := setupTest(t, "ddcn")
   446  	mctx := libkb.NewMetaContextForTest(*tc)
   447  
   448  	dismisser := &libkb.FakeGregorState{}
   449  	exceptedDeviceID := "active-device-id"
   450  	state := &FakeGregorState{
   451  		items: []gregor.Item{
   452  			buildGregorItem("anything.else", "a-device-id", "not-dismissable-1"),
   453  			buildGregorItem("device.new", exceptedDeviceID, "not-dismissable-2"),
   454  			buildGregorItem("device.new", "a-device-id", "dismissable-1"),
   455  			buildGregorItem("device.revoked", "another-device-id", "dismissable-2"),
   456  		},
   457  	}
   458  	expectedDismissedIDs := []gregor.MsgID{
   459  		gregor1.MsgID("dismissable-1"),
   460  		gregor1.MsgID("dismissable-2"),
   461  	}
   462  	require.Equal(t, []gregor.MsgID(nil), dismisser.PeekDismissedIDs())
   463  	err := service.LoopAndDismissForDeviceChangeNotifications(mctx, dismisser,
   464  		state, exceptedDeviceID)
   465  	require.NoError(t, err)
   466  	require.Equal(t, expectedDismissedIDs, dismisser.PeekDismissedIDs())
   467  }
   468  
   469  func idLiteArg(id keybase1.UserOrTeamID, assertion string) keybase1.IdentifyLiteArg {
   470  	return keybase1.IdentifyLiteArg{
   471  		Id:               id,
   472  		Assertion:        assertion,
   473  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI,
   474  	}
   475  }
   476  
   477  func TestIdentifyLite(t *testing.T) {
   478  	tt := newTeamTester(t)
   479  	defer tt.cleanup()
   480  
   481  	tt.addUser("abc")
   482  	teamName := tt.users[0].createTeam()
   483  	g := tt.users[0].tc.G
   484  
   485  	t.Logf("make a team")
   486  	team, err := GetTeamForTestByStringName(context.Background(), g, teamName)
   487  	require.NoError(t, err)
   488  
   489  	getTeamName := func(teamID keybase1.TeamID) keybase1.TeamName {
   490  		team, err := teams.Load(context.Background(), g, keybase1.LoadTeamArg{
   491  			ID: teamID,
   492  		})
   493  		require.NoError(t, err)
   494  		return team.Name()
   495  	}
   496  
   497  	t.Logf("make an implicit team")
   498  	iTeamCreateName := strings.Join([]string{tt.users[0].username, "bob@github"}, ",")
   499  	iTeam, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamCreateName, false /*isPublic*/)
   500  	require.NoError(t, err)
   501  	iTeamImpName := getTeamName(iTeam.ID)
   502  	require.True(t, iTeamImpName.IsImplicit())
   503  	require.NoError(t, err)
   504  
   505  	cli, err := client.GetIdentifyClient(g)
   506  	require.NoError(t, err, "failed to get new identifyclient")
   507  
   508  	// test ok assertions
   509  	var units = []struct {
   510  		assertion string
   511  		resID     keybase1.TeamID
   512  		resName   string
   513  	}{
   514  		{
   515  			assertion: "t_alice",
   516  			resName:   "t_alice",
   517  		}, {
   518  			assertion: "team:" + teamName,
   519  			resID:     team.ID,
   520  			resName:   teamName,
   521  		}, {
   522  			assertion: "tid:" + team.ID.String(),
   523  			resID:     team.ID,
   524  			resName:   teamName,
   525  		},
   526  	}
   527  	for _, unit := range units {
   528  		res, err := cli.IdentifyLite(context.Background(), idLiteArg("", unit.assertion))
   529  		require.NoError(t, err, "IdentifyLite (%s) failed", unit.assertion)
   530  
   531  		if len(unit.resID) > 0 {
   532  			require.Equal(t, unit.resID.String(), res.Ul.Id.String())
   533  		}
   534  
   535  		if len(unit.resName) > 0 {
   536  			require.Equal(t, unit.resName, res.Ul.Name)
   537  		}
   538  	}
   539  
   540  	// test identify by assertion and id
   541  	assertions := []string{"team:" + teamName, "tid:" + team.ID.String()}
   542  	for _, assertion := range assertions {
   543  		_, err := cli.IdentifyLite(context.Background(), idLiteArg(team.ID.AsUserOrTeam(), assertion))
   544  		require.NoError(t, err, "IdentifyLite by assertion and id (%s)", assertion)
   545  	}
   546  
   547  	// test identify by id only
   548  	_, err = cli.IdentifyLite(context.Background(), idLiteArg(team.ID.AsUserOrTeam(), ""))
   549  	require.NoError(t, err, "IdentifyLite id only")
   550  
   551  	// test invalid user format
   552  	_, err = cli.IdentifyLite(context.Background(), idLiteArg("", "__t_alice"))
   553  	require.Error(t, err)
   554  	require.Contains(t, err.Error(), "bad keybase username")
   555  
   556  	// test team read error
   557  	assertions = []string{"team:jwkj22111z"}
   558  	for _, assertion := range assertions {
   559  		_, err := cli.IdentifyLite(context.Background(), idLiteArg("", assertion))
   560  		aerr, ok := err.(libkb.AppStatusError)
   561  		if ok {
   562  			if aerr.Code != libkb.SCTeamNotFound {
   563  				t.Fatalf("app status code: %d, expected %d", aerr.Code, libkb.SCTeamNotFound)
   564  			}
   565  		} else {
   566  			require.True(t, regexp.MustCompile("Team .* does not exist").MatchString(err.Error()),
   567  				"Expected an AppStatusError or team-does-not-exist for %s, but got: %v (%T)", assertion, err, err)
   568  		}
   569  	}
   570  
   571  	// test not found assertions
   572  	assertions = []string{"t_weriojweroi"}
   573  	for _, assertion := range assertions {
   574  		_, err := cli.IdentifyLite(context.Background(), idLiteArg("", assertion))
   575  		if _, ok := err.(libkb.NotFoundError); !ok {
   576  			t.Fatalf("assertion %s, error: %s (%T), expected libkb.NotFoundError", assertion, err, err)
   577  		}
   578  	}
   579  }
   580  
   581  // test ResolveIdentifyImplicitTeam with a social assertion
   582  func TestResolveIdentifyImplicitTeamWithSocial(t *testing.T) {
   583  	tt := newTeamTester(t)
   584  	defer tt.cleanup()
   585  
   586  	tt.addUser("abc")
   587  	g := tt.users[0].tc.G
   588  
   589  	tt.addUser("wong")
   590  	wong := tt.users[1]
   591  	wong.proveRooter()
   592  
   593  	getTeamName := func(teamID keybase1.TeamID) keybase1.TeamName {
   594  		team, err := teams.Load(context.Background(), g, keybase1.LoadTeamArg{
   595  			ID: teamID,
   596  		})
   597  		require.NoError(t, err)
   598  		return team.Name()
   599  	}
   600  
   601  	iTeamNameCreate := strings.Join([]string{"bob@github", tt.users[0].username, wong.username}, ",")
   602  	// lookup with an assertion
   603  	iTeamNameLookup := strings.Join([]string{"bob@github", tt.users[0].username, wong.username + "@rooter"}, ",")
   604  	// the returned name should be sorted with the logged-in user first
   605  	iTeamNameSorted := strings.Join([]string{tt.users[0].username, "bob@github", wong.username}, ",")
   606  
   607  	t.Logf("make an implicit team")
   608  	iTeam, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate, false /*isPublic*/)
   609  	require.NoError(t, err)
   610  	iTeamImpName := getTeamName(iTeam.ID)
   611  	require.True(t, iTeamImpName.IsImplicit())
   612  
   613  	cli, err := client.GetIdentifyClient(g)
   614  	require.NoError(t, err, "failed to get new identifyclient")
   615  
   616  	res, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   617  		Assertions:       iTeamNameLookup,
   618  		Suffix:           "",
   619  		IsPublic:         false,
   620  		DoIdentifies:     false,
   621  		Create:           true,
   622  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   623  	})
   624  	require.NoError(t, err)
   625  	require.Equal(t, res.DisplayName, iTeamNameSorted)
   626  	require.Equal(t, res.TeamID, iTeam.ID)
   627  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
   628  	require.Nil(t, res.TrackBreaks, "track breaks")
   629  }
   630  
   631  // test ResolveIdentifyImplicitTeam with readers (also with DoIdentifies)
   632  func TestResolveIdentifyImplicitTeamWithReaders(t *testing.T) {
   633  	tt := newTeamTester(t)
   634  	defer tt.cleanup()
   635  
   636  	tt.addUser("abc")
   637  	g := tt.users[0].tc.G
   638  
   639  	tt.addUser("wong")
   640  	wong := tt.users[1]
   641  	wong.proveRooter()
   642  
   643  	iTeamNameCreate := tt.users[0].username + "#" + strings.Join([]string{"bob@github", wong.username}, ",")
   644  	// lookup with an assertion
   645  	iTeamNameLookup := tt.users[0].username + "#" + strings.Join([]string{"bob@github", wong.username + "@rooter"}, ",")
   646  
   647  	t.Logf("make an implicit team")
   648  	iTeam, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate, false /*isPublic*/)
   649  	require.NoError(t, err)
   650  
   651  	cli, err := client.GetIdentifyClient(g)
   652  	require.NoError(t, err, "failed to get new identifyclient")
   653  	attachIdentifyUI(t, g, newSimpleIdentifyUI())
   654  
   655  	res, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   656  		Assertions:       iTeamNameLookup,
   657  		Suffix:           "",
   658  		IsPublic:         false,
   659  		DoIdentifies:     true,
   660  		Create:           false,
   661  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   662  	})
   663  	require.NoError(t, err, "%v %v", err, spew.Sdump(res))
   664  	require.Equal(t, res.DisplayName, iTeamNameCreate)
   665  	require.Equal(t, res.TeamID, iTeam.ID)
   666  	require.Equal(t, []keybase1.UserVersion{tt.users[0].userVersion()}, res.Writers)
   667  	require.Nil(t, res.TrackBreaks, "track breaks")
   668  
   669  	t.Logf("Try getting the public team and fail (has nothing to do with readers)")
   670  	res, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   671  		Assertions:       iTeamNameCreate,
   672  		Suffix:           "",
   673  		IsPublic:         true,
   674  		DoIdentifies:     false,
   675  		Create:           false,
   676  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   677  	})
   678  	require.Error(t, err)
   679  	require.Regexp(t, `^Team.*does not exist$`, err.Error())
   680  }
   681  
   682  // test ResolveIdentifyImplicitTeam with duplicates
   683  func TestResolveIdentifyImplicitTeamWithDuplicates(t *testing.T) {
   684  	tt := newTeamTester(t)
   685  	defer tt.cleanup()
   686  
   687  	alice := tt.addUser("abc")
   688  	g := alice.tc.G
   689  
   690  	bob := tt.addUser("bob")
   691  
   692  	iTeamNameCreate := strings.Join([]string{alice.username, bob.username}, ",")
   693  	// simple duplicate
   694  	iTeamNameLookup1 := strings.Join([]string{alice.username, bob.username, bob.username}, ",")
   695  	// duplicate after resolution
   696  	iTeamNameLookup2 := strings.Join([]string{alice.username, bob.username, bob.username + "@rooter"}, ",")
   697  	// duplicate across reader boundary
   698  	iTeamNameLookup3 := strings.Join([]string{alice.username, bob.username + "@rooter"}, ",") + "#" + bob.username
   699  
   700  	t.Logf("make an implicit team")
   701  	iTeam, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate, false /*isPublic*/)
   702  	require.NoError(t, err)
   703  
   704  	bob.proveRooter()
   705  
   706  	cli, err := client.GetIdentifyClient(g)
   707  	require.NoError(t, err, "failed to get new identifyclient")
   708  
   709  	for i, lookup := range []string{iTeamNameLookup1, iTeamNameLookup2, iTeamNameLookup3} {
   710  		t.Logf("checking %v: %v", i, lookup)
   711  		res, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   712  			Assertions:       lookup,
   713  			Suffix:           "",
   714  			IsPublic:         false,
   715  			DoIdentifies:     false,
   716  			Create:           false,
   717  			IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   718  		})
   719  		require.NoError(t, err, "%v %v", err, spew.Sdump(res))
   720  		require.Equal(t, res.TeamID, iTeam.ID)
   721  		require.Equal(t, res.DisplayName, iTeamNameCreate)
   722  		require.True(t, compareUserVersionSets([]keybase1.UserVersion{alice.userVersion(), bob.userVersion()}, res.Writers))
   723  		require.Nil(t, res.TrackBreaks, "track breaks")
   724  	}
   725  }
   726  
   727  // test ResolveIdentifyImplicitTeam in offline mode
   728  func TestResolveIdentifyImplicitTeamOffline(t *testing.T) {
   729  	tt := newTeamTester(t)
   730  	defer tt.cleanup()
   731  
   732  	tt.addUser("abc")
   733  	g := tt.users[0].tc.G
   734  	tt.addUser("wong")
   735  	wong := tt.users[1]
   736  	wong.proveRooter()
   737  
   738  	// Set the ConnectivityMonitor in our test context
   739  	fcm := fakeConnectivityMonitor{}
   740  	fcm.Set(libkb.ConnectivityMonitorYes)
   741  	g.ConnectivityMonitor = &fcm
   742  	g.Env.Test.NoGregor = true
   743  
   744  	iTeamNameCreate := tt.users[0].username + "#" + strings.Join([]string{"bob@github", wong.username}, ",")
   745  	iTeamNameLookup := tt.users[0].username + "#" + strings.Join([]string{"bob@github", wong.username + "@rooter"}, ",")
   746  
   747  	t.Logf("make an implicit team")
   748  	iTeam, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate, false /*isPublic*/)
   749  	require.NoError(t, err)
   750  
   751  	cli, err := client.GetIdentifyClient(g)
   752  	require.NoError(t, err, "failed to get new identifyclient")
   753  	attachIdentifyUI(t, g, newSimpleIdentifyUI())
   754  
   755  	fetch := func() {
   756  		res, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   757  			Assertions:       iTeamNameLookup,
   758  			Suffix:           "",
   759  			IsPublic:         false,
   760  			DoIdentifies:     true,
   761  			Create:           false,
   762  			IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   763  			Oa:               keybase1.OfflineAvailability_BEST_EFFORT,
   764  		})
   765  		require.NoError(t, err, "%v %v", err, spew.Sdump(res))
   766  		require.Equal(t, res.DisplayName, iTeamNameCreate)
   767  		require.Equal(t, res.TeamID, iTeam.ID)
   768  		require.Equal(t, []keybase1.UserVersion{tt.users[0].userVersion()}, res.Writers)
   769  		require.Nil(t, res.TrackBreaks, "track breaks")
   770  	}
   771  
   772  	fetchFail := func(expectedError error, matchRegexp string) {
   773  		_, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   774  			Assertions:       iTeamNameCreate,
   775  			Suffix:           "",
   776  			IsPublic:         true,
   777  			DoIdentifies:     false,
   778  			Create:           false,
   779  			IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   780  			Oa:               keybase1.OfflineAvailability_BEST_EFFORT,
   781  		})
   782  		require.Error(t, err)
   783  		if expectedError != nil {
   784  			require.IsType(t, expectedError, err)
   785  		} else {
   786  			require.Regexp(t, matchRegexp, err.Error())
   787  		}
   788  	}
   789  
   790  	fetch()
   791  	fcm.Set(libkb.ConnectivityMonitorNo)
   792  	fetch()
   793  	fetchFail(libkb.OfflineError{}, "")
   794  	fcm.Set(libkb.ConnectivityMonitorYes)
   795  	fetch()
   796  	fetchFail(nil, `^Team.*does not exist$`)
   797  }
   798  
   799  func testResolveImplicitTeam(t *testing.T, g *libkb.GlobalContext, id keybase1.TeamID, isPublic bool, gen keybase1.Seqno) {
   800  	cli, err := client.GetIdentifyClient(g)
   801  	require.NoError(t, err, "failed to get new Identify client")
   802  	arg := keybase1.ResolveImplicitTeamArg{Id: id}
   803  	res, err := cli.ResolveImplicitTeam(context.Background(), arg)
   804  	require.NoError(t, err, "resolve Implicit team worked")
   805  	if gen == keybase1.Seqno(0) {
   806  		require.False(t, strings.Contains(res.Name, "conflicted"), "no conflicts")
   807  	} else {
   808  		require.True(t, strings.Contains(res.Name, "conflicted"), "found conflicted")
   809  		require.True(t, strings.Contains(res.Name, fmt.Sprintf("#%d", int(gen))), "found conflict gen #")
   810  	}
   811  }
   812  
   813  // doubleTestResolveImplicitTeam calls testResolveImplicitTeam twice, to make sure it
   814  // it works when we hit the cache (which we will do the second time through).
   815  func doubleTestResolveImplicitTeam(t *testing.T, g *libkb.GlobalContext, id keybase1.TeamID, isPublic bool, gen keybase1.Seqno) {
   816  	testResolveImplicitTeam(t, g, id, isPublic, gen)
   817  	testResolveImplicitTeam(t, g, id, isPublic, gen)
   818  }
   819  
   820  func TestResolveIdentifyImplicitTeamWithConflict(t *testing.T) {
   821  	tt := newTeamTester(t)
   822  	defer tt.cleanup()
   823  
   824  	tt.addUser("abc")
   825  	g := tt.users[0].tc.G
   826  
   827  	tt.addUser("wong")
   828  	wong := tt.users[1]
   829  
   830  	iTeamNameCreate1 := strings.Join([]string{tt.users[0].username, wong.username}, ",")
   831  	iTeamNameCreate2 := strings.Join([]string{tt.users[0].username, wong.username + "@rooter"}, ",")
   832  
   833  	t.Logf("make the teams")
   834  	iTeam1, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate1, false /*isPublic*/)
   835  	require.NoError(t, err)
   836  	iTeam2, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate2, false /*isPublic*/)
   837  	require.NoError(t, err)
   838  	require.NotEqual(t, iTeam1.ID, iTeam2.ID)
   839  	t.Logf("t1: %v", iTeam1.ID)
   840  	t.Logf("t2: %v", iTeam2.ID)
   841  
   842  	doubleTestResolveImplicitTeam(t, g, iTeam1.ID, false, keybase1.Seqno(0))
   843  	doubleTestResolveImplicitTeam(t, g, iTeam2.ID, false, keybase1.Seqno(0))
   844  
   845  	getTeamSeqno := func(teamID keybase1.TeamID) keybase1.Seqno {
   846  		team, err := teams.Load(context.Background(), g, keybase1.LoadTeamArg{
   847  			ID: teamID,
   848  		})
   849  		require.NoError(t, err)
   850  		return team.CurrentSeqno()
   851  	}
   852  
   853  	expectedSeqno := getTeamSeqno(iTeam2.ID) + 1
   854  
   855  	t.Logf("prove to create the conflict")
   856  	wong.proveRooter()
   857  
   858  	tt.users[0].waitForTeamChangedGregor(iTeam2.ID, expectedSeqno)
   859  
   860  	cli, err := client.GetIdentifyClient(g)
   861  	require.NoError(t, err, "failed to get new identifyclient")
   862  	iui := newSimpleIdentifyUI()
   863  	attachIdentifyUI(t, g, iui)
   864  
   865  	t.Logf("get the conflict winner team")
   866  	res, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   867  		Assertions:       iTeamNameCreate1,
   868  		Suffix:           "",
   869  		IsPublic:         false,
   870  		DoIdentifies:     true,
   871  		Create:           false,
   872  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   873  	})
   874  	require.NoError(t, err, "%v %v", err, spew.Sdump(res))
   875  	require.Equal(t, res.DisplayName, iTeamNameCreate1)
   876  	require.Equal(t, res.TeamID, iTeam1.ID)
   877  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
   878  	require.Nil(t, res.TrackBreaks, "track breaks")
   879  
   880  	t.Logf("get the conflict winner using the assertion name")
   881  	res, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   882  		Assertions:       iTeamNameCreate2,
   883  		Suffix:           "",
   884  		IsPublic:         false,
   885  		DoIdentifies:     true,
   886  		Create:           false,
   887  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   888  	})
   889  	require.NoError(t, err, "%v %v", err, spew.Sdump(res))
   890  	require.Equal(t, res.DisplayName, iTeamNameCreate1)
   891  	require.Equal(t, res.TeamID, iTeam1.ID)
   892  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
   893  	require.Nil(t, res.TrackBreaks, "track breaks")
   894  
   895  	t.Logf("find out the conflict suffix")
   896  	iTeamxx, _, _, conflicts, err := teams.LookupImplicitTeamAndConflicts(context.TODO(), g, iTeamNameCreate1, false /*isPublic*/, teams.ImplicitTeamOptions{})
   897  	require.NoError(t, err)
   898  	require.Equal(t, iTeamxx.ID, iTeam1.ID)
   899  	require.Len(t, conflicts, 1)
   900  
   901  	t.Logf("get the conflict loser")
   902  	res, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   903  		Assertions:       iTeamNameCreate1,
   904  		Suffix:           libkb.FormatImplicitTeamDisplayNameSuffix(conflicts[0]),
   905  		IsPublic:         false,
   906  		DoIdentifies:     true,
   907  		Create:           false,
   908  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   909  	})
   910  	require.NoError(t, err, "%v %v", err, spew.Sdump(res))
   911  	require.Equal(t, res.DisplayName, iTeamNameCreate1+" "+libkb.FormatImplicitTeamDisplayNameSuffix(conflicts[0]))
   912  	require.Equal(t, res.TeamID, iTeam2.ID)
   913  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
   914  	require.Nil(t, res.TrackBreaks, "track breaks")
   915  
   916  	testResolveImplicitTeam(t, g, iTeam1.ID, false, keybase1.Seqno(0))
   917  	testResolveImplicitTeam(t, g, iTeam2.ID, false, keybase1.Seqno(1))
   918  
   919  }
   920  
   921  func TestResolveIdentifyImplicitTeamWithIdentifyFailures(t *testing.T) {
   922  	tt := newTeamTester(t)
   923  	defer tt.cleanup()
   924  
   925  	tt.addUser("abc")
   926  	g := tt.users[0].tc.G
   927  
   928  	tt.addUser("wong")
   929  	wong := tt.users[1]
   930  
   931  	iTeamNameCreate := strings.Join([]string{tt.users[0].username, wong.username}, ",")
   932  
   933  	t.Logf("make an implicit team")
   934  	iTeam, _, _, err := teams.LookupOrCreateImplicitTeam(context.TODO(), g, iTeamNameCreate, false /*isPublic*/)
   935  	require.NoError(t, err)
   936  
   937  	cli, err := client.GetIdentifyClient(g)
   938  	require.NoError(t, err, "failed to get new identifyclient")
   939  	iui := newSimpleIdentifyUI()
   940  	attachIdentifyUI(t, g, iui)
   941  
   942  	t.Logf("try but fail on assertion")
   943  	res, err := cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   944  		// lookup with a compound assertion, the first part will resolve, the second part will fail
   945  		Assertions:       strings.Join([]string{tt.users[0].username, wong.username + "&&" + wong.username + "@rooter"}, ","),
   946  		Suffix:           "",
   947  		IsPublic:         false,
   948  		DoIdentifies:     true,
   949  		Create:           false,
   950  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   951  	})
   952  	require.Error(t, err)
   953  	require.IsType(t, libkb.IdentifiesFailedError{}, err, "%v", err)
   954  	require.Equal(t, res.DisplayName, iTeamNameCreate)
   955  	require.Equal(t, res.TeamID, iTeam.ID)
   956  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
   957  	require.Nil(t, res.TrackBreaks, "expect no track breaks")
   958  
   959  	t.Logf("prove rooter and track")
   960  	g.ProofCache.DisableDisk()
   961  	wong.proveRooter()
   962  	iui.confirmRes = keybase1.ConfirmResult{IdentityConfirmed: true, RemoteConfirmed: true, AutoConfirmed: true}
   963  	tt.users[0].track(wong.username)
   964  	iui.confirmRes = keybase1.ConfirmResult{}
   965  
   966  	t.Logf("make rooter unreachable")
   967  	g.XAPI = &flakeyRooterAPI{orig: g.XAPI, hardFail: true, G: g}
   968  	err = g.ProofCache.Reset()
   969  	require.NoError(t, err)
   970  
   971  	t.Logf("try but fail on tracking (1)")
   972  	res, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   973  		// lookup by username, but the dead rooter proof fails our tracking
   974  		Assertions:       strings.Join([]string{tt.users[0].username, wong.username}, ","),
   975  		Suffix:           "",
   976  		IsPublic:         false,
   977  		DoIdentifies:     true,
   978  		Create:           false,
   979  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
   980  	})
   981  	require.Error(t, err)
   982  	require.IsType(t, libkb.IdentifiesFailedError{}, err, "%v", err)
   983  	require.Equal(t, res.DisplayName, iTeamNameCreate)
   984  	require.Equal(t, res.TeamID, iTeam.ID)
   985  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
   986  	require.Nil(t, res.TrackBreaks) // counter-intuitively, there are no track breaks when the error is fatal in this mode.
   987  
   988  	t.Logf("try but fail on tracking (2)")
   989  	res, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
   990  		// lookup by username, but the dead rooter proof fails our tracking
   991  		Assertions:       strings.Join([]string{tt.users[0].username, wong.username}, ","),
   992  		Suffix:           "",
   993  		IsPublic:         false,
   994  		DoIdentifies:     true,
   995  		Create:           false,
   996  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_CHAT_CLI, // Pass a weird IdentifyBehavior to get TrackBreaks to come out.
   997  	})
   998  	require.Error(t, err)
   999  	require.IsType(t, libkb.IdentifiesFailedError{}, err, "%v", err)
  1000  	require.Equal(t, res.DisplayName, iTeamNameCreate)
  1001  	require.Equal(t, res.TeamID, iTeam.ID)
  1002  	require.True(t, compareUserVersionSets([]keybase1.UserVersion{tt.users[0].userVersion(), wong.userVersion()}, res.Writers))
  1003  	// In this mode, in addition to the error TrackBreaks is filled.
  1004  	require.NotNil(t, res.TrackBreaks)
  1005  	require.NotNil(t, res.TrackBreaks[wong.userVersion()])
  1006  	require.Len(t, res.TrackBreaks[wong.userVersion()].Proofs, 1)
  1007  	require.Equal(t, keybase1.ProofType_ROOTER, res.TrackBreaks[wong.userVersion()].Proofs[0].RemoteProof.ProofType)
  1008  }
  1009  
  1010  func TestResolveIdentifyImplicitTeamWithIdentifyBadInput(t *testing.T) {
  1011  	tt := newTeamTester(t)
  1012  	defer tt.cleanup()
  1013  
  1014  	tt.addUser("abc")
  1015  	g := tt.users[0].tc.G
  1016  
  1017  	cli, err := client.GetIdentifyClient(g)
  1018  	require.NoError(t, err, "failed to get new identifyclient")
  1019  	attachIdentifyUI(t, g, newSimpleIdentifyUI())
  1020  
  1021  	_, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
  1022  		Assertions:       "", // blank assertions
  1023  		Suffix:           "",
  1024  		IsPublic:         false,
  1025  		DoIdentifies:     true,
  1026  		Create:           true,
  1027  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
  1028  	})
  1029  	require.Error(t, err)
  1030  	t.Logf("err: %v", err)
  1031  
  1032  	_, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
  1033  		Assertions:       tt.users[0].username,
  1034  		Suffix:           "bad suffix",
  1035  		IsPublic:         false,
  1036  		DoIdentifies:     true,
  1037  		Create:           true,
  1038  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
  1039  	})
  1040  	require.Error(t, err)
  1041  	t.Logf("err: %v", err)
  1042  
  1043  	_, err = cli.ResolveIdentifyImplicitTeam(context.Background(), keybase1.ResolveIdentifyImplicitTeamArg{
  1044  		Assertions:       "malformed #)*$&#) assertion",
  1045  		Suffix:           "",
  1046  		IsPublic:         true,
  1047  		DoIdentifies:     true,
  1048  		Create:           false,
  1049  		IdentifyBehavior: keybase1.TLFIdentifyBehavior_DEFAULT_KBFS,
  1050  	})
  1051  	require.Error(t, err)
  1052  	t.Logf("err: %v", err)
  1053  }
  1054  
  1055  // golang
  1056  func compareUserVersionSets(xs1 []keybase1.UserVersion, xs2 []keybase1.UserVersion) bool {
  1057  	if len(xs1) != len(xs2) {
  1058  		return false
  1059  	}
  1060  	var ys1 []keybase1.UserVersion
  1061  	ys1 = append(ys1, xs1...)
  1062  	var ys2 []keybase1.UserVersion
  1063  	ys2 = append(ys2, xs2...)
  1064  	cmp := func(a, b keybase1.UserVersion) bool {
  1065  		if a.Uid.Equal(b.Uid) {
  1066  			return a.EldestSeqno < b.EldestSeqno
  1067  		}
  1068  		return a.Uid < b.Uid
  1069  	}
  1070  	sort.Slice(ys1, func(i, j int) bool { return cmp(ys1[i], ys1[j]) })
  1071  	sort.Slice(ys2, func(i, j int) bool { return cmp(ys2[i], ys2[j]) })
  1072  	for i, y1 := range ys1 {
  1073  		if !y1.Eq(ys2[i]) {
  1074  			return false
  1075  		}
  1076  	}
  1077  	return true
  1078  }
  1079  
  1080  func attachIdentifyUI(t *testing.T, g *libkb.GlobalContext, iui keybase1.IdentifyUiInterface) {
  1081  	cli, xp, err := client.GetRPCClientWithContext(g)
  1082  	require.NoError(t, err)
  1083  	srv := rpc.NewServer(xp, nil)
  1084  	err = srv.Register(keybase1.IdentifyUiProtocol(iui))
  1085  	require.NoError(t, err)
  1086  	ncli := keybase1.DelegateUiCtlClient{Cli: cli}
  1087  	err = ncli.RegisterIdentifyUI(context.TODO())
  1088  	require.NoError(t, err)
  1089  }
  1090  
  1091  func simpleIdentifyUIError(s string) error {
  1092  	return fmt.Errorf("simpleIdentifyUI does not support %v", s)
  1093  }
  1094  
  1095  type simpleIdentifyUI struct {
  1096  	delegated  bool
  1097  	confirmRes keybase1.ConfirmResult
  1098  }
  1099  
  1100  func newSimpleIdentifyUI() *simpleIdentifyUI {
  1101  	return &simpleIdentifyUI{}
  1102  }
  1103  
  1104  var _ keybase1.IdentifyUiInterface = (*simpleIdentifyUI)(nil)
  1105  
  1106  func (p *simpleIdentifyUI) DelegateIdentifyUI(context.Context) (int, error) {
  1107  	p.delegated = true
  1108  	return 1, nil
  1109  }
  1110  func (p *simpleIdentifyUI) Start(context.Context, keybase1.StartArg) error {
  1111  	return nil
  1112  }
  1113  func (p *simpleIdentifyUI) DisplayKey(context.Context, keybase1.DisplayKeyArg) error {
  1114  	return simpleIdentifyUIError("DisplayKey")
  1115  }
  1116  func (p *simpleIdentifyUI) ReportLastTrack(context.Context, keybase1.ReportLastTrackArg) error {
  1117  	return nil
  1118  }
  1119  func (p *simpleIdentifyUI) LaunchNetworkChecks(_ context.Context, arg keybase1.LaunchNetworkChecksArg) error {
  1120  	return nil
  1121  }
  1122  func (p *simpleIdentifyUI) DisplayTrackStatement(context.Context, keybase1.DisplayTrackStatementArg) error {
  1123  	return simpleIdentifyUIError("DisplayTrackStatement")
  1124  }
  1125  func (p *simpleIdentifyUI) ReportTrackToken(context.Context, keybase1.ReportTrackTokenArg) error {
  1126  	return nil
  1127  }
  1128  func (p *simpleIdentifyUI) FinishWebProofCheck(context.Context, keybase1.FinishWebProofCheckArg) error {
  1129  	return simpleIdentifyUIError("FinishWebProofCheck")
  1130  }
  1131  func (p *simpleIdentifyUI) FinishSocialProofCheck(_ context.Context, arg keybase1.FinishSocialProofCheckArg) error {
  1132  	return nil
  1133  }
  1134  func (p *simpleIdentifyUI) DisplayCryptocurrency(context.Context, keybase1.DisplayCryptocurrencyArg) error {
  1135  	return simpleIdentifyUIError("DisplayCryptocurrency")
  1136  }
  1137  func (p *simpleIdentifyUI) DisplayStellarAccount(context.Context, keybase1.DisplayStellarAccountArg) error {
  1138  	return simpleIdentifyUIError("DisplayStellarAccount")
  1139  }
  1140  func (p *simpleIdentifyUI) DisplayUserCard(context.Context, keybase1.DisplayUserCardArg) error {
  1141  	return nil
  1142  }
  1143  func (p *simpleIdentifyUI) Confirm(context.Context, keybase1.ConfirmArg) (res keybase1.ConfirmResult, err error) {
  1144  	return p.confirmRes, nil
  1145  }
  1146  func (p *simpleIdentifyUI) Cancel(context.Context, int) error {
  1147  	return nil
  1148  }
  1149  func (p *simpleIdentifyUI) Finish(context.Context, int) error {
  1150  	return nil
  1151  }
  1152  func (p *simpleIdentifyUI) Dismiss(context.Context, keybase1.DismissArg) error {
  1153  	return simpleIdentifyUIError("Dismiss")
  1154  }
  1155  func (p *simpleIdentifyUI) DisplayTLFCreateWithInvite(context.Context, keybase1.DisplayTLFCreateWithInviteArg) error {
  1156  	return simpleIdentifyUIError("DisplayTLFCreateWithInvite")
  1157  }
  1158  
  1159  // copied from engine tests
  1160  type flakeyRooterAPI struct {
  1161  	orig     libkb.ExternalAPI
  1162  	flakeOut bool
  1163  	hardFail bool
  1164  	G        *libkb.GlobalContext
  1165  }
  1166  
  1167  func (e *flakeyRooterAPI) GetText(m libkb.MetaContext, arg libkb.APIArg) (*libkb.ExternalTextRes, error) {
  1168  	m.Debug("| flakeyRooterAPI.GetText, hard = %v, flake = %v", e.hardFail, e.flakeOut)
  1169  	return e.orig.GetText(m, arg)
  1170  }
  1171  
  1172  func (e *flakeyRooterAPI) Get(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalAPIRes, err error) {
  1173  	m.Debug("| flakeyRooterAPI.Get, hard = %v, flake = %v", e.hardFail, e.flakeOut)
  1174  	// Show an error if we're in flakey mode
  1175  	if strings.Contains(arg.Endpoint, "rooter") {
  1176  		if e.hardFail {
  1177  			return &libkb.ExternalAPIRes{HTTPStatus: 404}, &libkb.APIError{Msg: "NotFound", Code: 404}
  1178  		}
  1179  		if e.flakeOut {
  1180  			return &libkb.ExternalAPIRes{HTTPStatus: 429}, &libkb.APIError{Msg: "Ratelimited", Code: 429}
  1181  		}
  1182  	}
  1183  
  1184  	return e.orig.Get(m, arg)
  1185  }
  1186  
  1187  func (e *flakeyRooterAPI) GetHTML(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalHTMLRes, err error) {
  1188  	m.Debug("| flakeyRooterAPI.GetHTML, hard = %v, flake = %v", e.hardFail, e.flakeOut)
  1189  	return e.orig.GetHTML(m, arg)
  1190  }
  1191  
  1192  func (e *flakeyRooterAPI) Post(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalAPIRes, err error) {
  1193  	return e.orig.Post(m, arg)
  1194  }
  1195  
  1196  func (e *flakeyRooterAPI) PostHTML(m libkb.MetaContext, arg libkb.APIArg) (res *libkb.ExternalHTMLRes, err error) {
  1197  	return e.orig.PostHTML(m, arg)
  1198  }