github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/systests/treeloader_test.go (about)

     1  package systests
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"testing"
     7  	"time"
     8  
     9  	"golang.org/x/net/context"
    10  
    11  	"github.com/keybase/client/go/libkb"
    12  	"github.com/keybase/client/go/protocol/keybase1"
    13  	"github.com/keybase/client/go/teams"
    14  	"github.com/stretchr/testify/require"
    15  )
    16  
    17  func mustAppend(t *testing.T, a keybase1.TeamName, b string) keybase1.TeamName {
    18  	ret, err := a.Append(b)
    19  	require.NoError(t, err)
    20  	return ret
    21  }
    22  
    23  func mustCreateSubteam(t *testing.T, tc *libkb.TestContext,
    24  	name keybase1.TeamName) keybase1.TeamID {
    25  	parent, err := name.Parent()
    26  	require.NoError(t, err)
    27  	id, err := teams.CreateSubteam(context.TODO(), tc.G, name.LastPart().String(),
    28  		parent, keybase1.TeamRole_NONE)
    29  	require.NoError(t, err)
    30  	return *id
    31  }
    32  
    33  func loadTeamTree(t *testing.T, tmctx libkb.MetaContext, notifications *teamNotifyHandler,
    34  	teamID keybase1.TeamID, username string, failureTeamIDs []keybase1.TeamID,
    35  	teamFailures []string) ([]keybase1.TeamTreeMembership, error) {
    36  	var err error
    37  
    38  	guid := rand.Int()
    39  
    40  	l, err := teams.NewTreeloader(tmctx, username, teamID, guid, true /* includeAncestors */)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	if failureTeamIDs != nil {
    45  		l.Converter = newMockConverter(failureTeamIDs, l)
    46  	}
    47  	err = l.LoadAsync(tmctx)
    48  	if err != nil {
    49  		return nil, err
    50  	}
    51  
    52  	var results []keybase1.TeamTreeMembership
    53  	var expectedCount *int
    54  	got := 0
    55  loop:
    56  	for {
    57  		select {
    58  		case res := <-notifications.teamTreeMembershipsDoneCh:
    59  			// We don't immediately break in this case because we are not guaranteed to receive this
    60  			// notification last by the RPC layer. So we wait until all of the messages are
    61  			// received. Even if there were errors, we should still get exactly this many
    62  			// notifications in teamTreeMembershipsPartialCh.
    63  			expectedCount = &res.ExpectedCount
    64  			require.Equal(t, guid, res.Guid)
    65  		case res := <-notifications.teamTreeMembershipsPartialCh:
    66  			got++
    67  			results = append(results, res)
    68  			s, err := res.Result.S()
    69  			require.NoError(t, err, "should never happen")
    70  			switch s {
    71  			case keybase1.TeamTreeMembershipStatus_OK:
    72  			case keybase1.TeamTreeMembershipStatus_ERROR:
    73  				require.Contains(t, teamFailures, res.TeamName,
    74  					"unexpectedly got an error while loading team: %s", res.Result.Error().Message)
    75  			}
    76  			require.Equal(t, guid, res.Guid)
    77  		case <-time.After(10 * time.Second):
    78  			t.Fatalf("timed out waiting for team tree notifications")
    79  		}
    80  		if expectedCount != nil && *expectedCount == got {
    81  			break loop
    82  		}
    83  	}
    84  	return results, nil
    85  }
    86  
    87  func checkTeamTreeResults(t *testing.T, expected map[string]keybase1.TeamRole,
    88  	failureTeamNames []string, hiddenTeamNames []string, results []keybase1.TeamTreeMembership) {
    89  	require.Equal(t, len(expected)+len(failureTeamNames)+len(hiddenTeamNames),
    90  		len(results), "got right number of results back")
    91  	m := make(map[string]struct{})
    92  	for _, result := range results {
    93  		_, alreadyExists := m[result.TeamName]
    94  		require.False(t, alreadyExists, "got a duplicate got %s", result.TeamName)
    95  		m[result.TeamName] = struct{}{}
    96  		s, err := result.Result.S()
    97  		require.NoError(t, err)
    98  
    99  		switch s {
   100  		case keybase1.TeamTreeMembershipStatus_OK:
   101  			r, ok := expected[result.TeamName]
   102  			require.True(t, ok, "should not have gotten a result for %s", result.TeamName)
   103  			val := result.Result.Ok()
   104  			require.Equal(t, r, val.Role, "expected role %v for team %s, but got role %v",
   105  				r, result.TeamName, val.Role)
   106  			if val.Role != keybase1.TeamRole_NONE {
   107  				require.NotNil(t, val.JoinTime)
   108  			}
   109  		case keybase1.TeamTreeMembershipStatus_ERROR:
   110  			require.Contains(t, failureTeamNames, result.TeamName)
   111  		case keybase1.TeamTreeMembershipStatus_HIDDEN:
   112  			require.Contains(t, hiddenTeamNames, result.TeamName)
   113  		default:
   114  			t.Errorf("got an unknown result status %s", s)
   115  		}
   116  	}
   117  }
   118  
   119  type mockConverter struct {
   120  	failureTeamIDs []keybase1.TeamID
   121  	loader         *teams.Treeloader
   122  }
   123  
   124  func (m mockConverter) ProcessSigchainState(mctx libkb.MetaContext,
   125  	teamName keybase1.TeamName, s *keybase1.TeamSigChainState) keybase1.TeamTreeMembershipResult {
   126  	for _, failureTeamID := range m.failureTeamIDs {
   127  		if failureTeamID == s.Id {
   128  			return m.loader.NewErrorResult(fmt.Errorf("mock failure"), teamName)
   129  		}
   130  	}
   131  	return m.loader.ProcessSigchainState(mctx, teamName, s)
   132  }
   133  
   134  func newMockConverter(failureTeamIDs []keybase1.TeamID, loader *teams.Treeloader) mockConverter {
   135  	return mockConverter{
   136  		failureTeamIDs: failureTeamIDs,
   137  		loader:         loader,
   138  	}
   139  }
   140  
   141  func TestLoadTeamTreeMemberships(t *testing.T) {
   142  	tt := newTeamTester(t)
   143  	defer tt.cleanup()
   144  
   145  	t.Logf("Creating users")
   146  	// Create the folowing team tree:
   147  	//
   148  	//     .___A_____.
   149  	//     |         |
   150  	//     B     .___C__.
   151  	//           |      |
   152  	//           D   .__E__.
   153  	//           |   |  |  |
   154  	//           F   G  H  I
   155  	//
   156  	// Teams are going to have the following members:
   157  	//
   158  	// A: zulu (adm)
   159  	// B: yank (adm)
   160  	// C: yank (adm), vict, tang
   161  	// D: xray (adm), unif
   162  	// E: tang (adm), vict
   163  	// F: yank (adm)
   164  	// G: yank, whis (adm), vict
   165  	// H: zulu, xray
   166  	// I: zulu, unif
   167  	// Below, we generate paperkeys to set up a environment for testing the GUI integration.
   168  	alfa := tt.addUserWithPaper("alfa")
   169  	t.Logf("Generated paperkey for %s: %s", alfa.username, alfa.backupKey.secret)
   170  	zulu := tt.addUserWithPaper("zulu")
   171  	t.Logf("Generated paperkey for %s: %s", zulu.username, zulu.backupKey.secret)
   172  	yank := tt.addUserWithPaper("yank")
   173  	t.Logf("Generated paperkey for %s: %s", yank.username, yank.backupKey.secret)
   174  	xray := tt.addUserWithPaper("xray")
   175  	t.Logf("Generated paperkey for %s: %s", xray.username, xray.backupKey.secret)
   176  	whis := tt.addUserWithPaper("whis")
   177  	t.Logf("Generated paperkey for %s: %s", whis.username, whis.backupKey.secret)
   178  	vict := tt.addUserWithPaper("vict")
   179  	t.Logf("Generated paperkey for %s: %s", vict.username, vict.backupKey.secret)
   180  	unif := tt.addUserWithPaper("unif")
   181  	t.Logf("UGenerated paperkey for %s: %s", unif.username, unif.backupKey.secret)
   182  	tang := tt.addUserWithPaper("tang")
   183  	t.Logf("Generated paperkey for %s: %s", tang.username, tang.backupKey.secret)
   184  
   185  	t.Logf("Creating teams")
   186  	aID, aName := alfa.createTeam2()
   187  	bName := mustAppend(t, aName, "bb")
   188  	cName := mustAppend(t, aName, "cc")
   189  	dName := mustAppend(t, cName, "dd")
   190  	eName := mustAppend(t, cName, "ee")
   191  	fName := mustAppend(t, dName, "ff")
   192  	gName := mustAppend(t, eName, "gg")
   193  	hName := mustAppend(t, eName, "hh")
   194  	iName := mustAppend(t, eName, "ii")
   195  	bID := mustCreateSubteam(t, alfa.tc, bName)
   196  	cID := mustCreateSubteam(t, alfa.tc, cName)
   197  	dID := mustCreateSubteam(t, alfa.tc, dName)
   198  	eID := mustCreateSubteam(t, alfa.tc, eName)
   199  	fID := mustCreateSubteam(t, alfa.tc, fName)
   200  	gID := mustCreateSubteam(t, alfa.tc, gName)
   201  	hID := mustCreateSubteam(t, alfa.tc, hName)
   202  	iID := mustCreateSubteam(t, alfa.tc, iName)
   203  
   204  	var err error
   205  
   206  	t.Logf("Populating teams with members")
   207  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, aID,
   208  		[]keybase1.UserRolePair{
   209  			{Assertion: zulu.username, Role: keybase1.TeamRole_ADMIN},
   210  		},
   211  		nil,
   212  	)
   213  	require.NoError(t, err)
   214  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, bID,
   215  		[]keybase1.UserRolePair{
   216  			{Assertion: yank.username, Role: keybase1.TeamRole_ADMIN},
   217  		},
   218  		nil,
   219  	)
   220  	require.NoError(t, err)
   221  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, cID,
   222  		[]keybase1.UserRolePair{
   223  			{Assertion: yank.username, Role: keybase1.TeamRole_ADMIN},
   224  			{Assertion: vict.username, Role: keybase1.TeamRole_WRITER},
   225  			{Assertion: tang.username, Role: keybase1.TeamRole_WRITER},
   226  		},
   227  		nil,
   228  	)
   229  	require.NoError(t, err)
   230  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, dID,
   231  		[]keybase1.UserRolePair{
   232  			{Assertion: xray.username, Role: keybase1.TeamRole_ADMIN},
   233  			{Assertion: unif.username, Role: keybase1.TeamRole_WRITER},
   234  		},
   235  		nil,
   236  	)
   237  	require.NoError(t, err)
   238  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, eID,
   239  		[]keybase1.UserRolePair{
   240  			{Assertion: tang.username, Role: keybase1.TeamRole_ADMIN},
   241  			{Assertion: vict.username, Role: keybase1.TeamRole_WRITER},
   242  		},
   243  		nil,
   244  	)
   245  	require.NoError(t, err)
   246  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, fID,
   247  		[]keybase1.UserRolePair{
   248  			{Assertion: yank.username, Role: keybase1.TeamRole_ADMIN},
   249  		},
   250  		nil,
   251  	)
   252  	require.NoError(t, err)
   253  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, gID,
   254  		[]keybase1.UserRolePair{
   255  			{Assertion: yank.username, Role: keybase1.TeamRole_WRITER},
   256  			{Assertion: whis.username, Role: keybase1.TeamRole_ADMIN},
   257  			{Assertion: vict.username, Role: keybase1.TeamRole_WRITER},
   258  		},
   259  		nil,
   260  	)
   261  	require.NoError(t, err)
   262  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, hID,
   263  		[]keybase1.UserRolePair{
   264  			{Assertion: zulu.username, Role: keybase1.TeamRole_WRITER},
   265  			{Assertion: xray.username, Role: keybase1.TeamRole_WRITER},
   266  		},
   267  		nil,
   268  	)
   269  	require.NoError(t, err)
   270  	_, _, err = teams.AddMembers(context.Background(), alfa.tc.G, iID,
   271  		[]keybase1.UserRolePair{
   272  			{Assertion: zulu.username, Role: keybase1.TeamRole_WRITER},
   273  			{Assertion: unif.username, Role: keybase1.TeamRole_WRITER},
   274  		},
   275  		nil,
   276  	)
   277  	require.NoError(t, err)
   278  
   279  	t.Logf("Modifying teams")
   280  	tui := &teamsUI{}
   281  	err = teams.Delete(context.Background(), alfa.tc.G, tui, hID)
   282  	require.NoError(t, err)
   283  	tang.reset()
   284  	tang.loginAfterReset()
   285  	unif.delete()
   286  
   287  	t.Logf("happy-path table tests")
   288  	tsts := []struct {
   289  		teamID    keybase1.TeamID
   290  		requester *userPlusDevice
   291  		target    *userPlusDevice
   292  		hidden    []string
   293  		expected  map[string]keybase1.TeamRole
   294  	}{
   295  		{
   296  			teamID:    aID,
   297  			requester: zulu,
   298  			target:    zulu,
   299  			expected: map[string]keybase1.TeamRole{
   300  				aName.String(): keybase1.TeamRole_ADMIN,
   301  				bName.String(): keybase1.TeamRole_NONE,
   302  				cName.String(): keybase1.TeamRole_NONE,
   303  				dName.String(): keybase1.TeamRole_NONE,
   304  				eName.String(): keybase1.TeamRole_NONE,
   305  				fName.String(): keybase1.TeamRole_NONE,
   306  				gName.String(): keybase1.TeamRole_NONE,
   307  				iName.String(): keybase1.TeamRole_WRITER,
   308  			},
   309  			hidden: []string{},
   310  		},
   311  		{
   312  			teamID:    bID,
   313  			requester: zulu,
   314  			target:    zulu,
   315  			expected: map[string]keybase1.TeamRole{
   316  				aName.String(): keybase1.TeamRole_ADMIN,
   317  				bName.String(): keybase1.TeamRole_NONE,
   318  			},
   319  			hidden: []string{},
   320  		},
   321  		{
   322  			teamID:    bID,
   323  			requester: zulu,
   324  			target:    yank,
   325  			expected: map[string]keybase1.TeamRole{
   326  				aName.String(): keybase1.TeamRole_NONE,
   327  				bName.String(): keybase1.TeamRole_ADMIN,
   328  			},
   329  		},
   330  		{
   331  			teamID:    bID,
   332  			requester: yank,
   333  			target:    zulu,
   334  			expected: map[string]keybase1.TeamRole{
   335  				bName.String(): keybase1.TeamRole_NONE,
   336  			},
   337  			hidden: []string{aName.String()},
   338  		},
   339  		{
   340  			teamID:    bID,
   341  			requester: yank,
   342  			target:    yank,
   343  			expected: map[string]keybase1.TeamRole{
   344  				bName.String(): keybase1.TeamRole_ADMIN,
   345  			},
   346  			hidden: []string{aName.String()},
   347  		},
   348  		{
   349  			teamID:    cID,
   350  			requester: zulu,
   351  			target:    yank,
   352  			expected: map[string]keybase1.TeamRole{
   353  				aName.String(): keybase1.TeamRole_NONE,
   354  				cName.String(): keybase1.TeamRole_ADMIN,
   355  				dName.String(): keybase1.TeamRole_NONE,
   356  				eName.String(): keybase1.TeamRole_NONE,
   357  				fName.String(): keybase1.TeamRole_ADMIN,
   358  				gName.String(): keybase1.TeamRole_WRITER,
   359  				iName.String(): keybase1.TeamRole_NONE,
   360  			},
   361  		},
   362  		{
   363  			teamID:    cID,
   364  			requester: yank,
   365  			target:    yank,
   366  			expected: map[string]keybase1.TeamRole{
   367  				cName.String(): keybase1.TeamRole_ADMIN,
   368  				dName.String(): keybase1.TeamRole_NONE,
   369  				eName.String(): keybase1.TeamRole_NONE,
   370  				fName.String(): keybase1.TeamRole_ADMIN,
   371  				gName.String(): keybase1.TeamRole_WRITER,
   372  				iName.String(): keybase1.TeamRole_NONE,
   373  			},
   374  			hidden: []string{aName.String()},
   375  		},
   376  		{
   377  			teamID:    cID,
   378  			requester: zulu,
   379  			target:    whis,
   380  			expected: map[string]keybase1.TeamRole{
   381  				aName.String(): keybase1.TeamRole_NONE,
   382  				cName.String(): keybase1.TeamRole_NONE,
   383  				dName.String(): keybase1.TeamRole_NONE,
   384  				eName.String(): keybase1.TeamRole_NONE,
   385  				fName.String(): keybase1.TeamRole_NONE,
   386  				gName.String(): keybase1.TeamRole_ADMIN,
   387  				iName.String(): keybase1.TeamRole_NONE,
   388  			},
   389  		},
   390  		{
   391  			teamID:    cID,
   392  			requester: yank,
   393  			target:    whis,
   394  			expected: map[string]keybase1.TeamRole{
   395  				cName.String(): keybase1.TeamRole_NONE,
   396  				dName.String(): keybase1.TeamRole_NONE,
   397  				eName.String(): keybase1.TeamRole_NONE,
   398  				fName.String(): keybase1.TeamRole_NONE,
   399  				gName.String(): keybase1.TeamRole_ADMIN,
   400  				iName.String(): keybase1.TeamRole_NONE,
   401  			},
   402  			hidden: []string{aName.String()},
   403  		},
   404  
   405  		{
   406  			teamID:    cID,
   407  			requester: zulu,
   408  			target:    xray,
   409  			expected: map[string]keybase1.TeamRole{
   410  				aName.String(): keybase1.TeamRole_NONE,
   411  				cName.String(): keybase1.TeamRole_NONE,
   412  				dName.String(): keybase1.TeamRole_ADMIN,
   413  				eName.String(): keybase1.TeamRole_NONE,
   414  				fName.String(): keybase1.TeamRole_NONE,
   415  				gName.String(): keybase1.TeamRole_NONE,
   416  				iName.String(): keybase1.TeamRole_NONE,
   417  			},
   418  		},
   419  		{
   420  			teamID:    cID,
   421  			requester: yank,
   422  			target:    xray,
   423  			expected: map[string]keybase1.TeamRole{
   424  				cName.String(): keybase1.TeamRole_NONE,
   425  				dName.String(): keybase1.TeamRole_ADMIN,
   426  				eName.String(): keybase1.TeamRole_NONE,
   427  				fName.String(): keybase1.TeamRole_NONE,
   428  				gName.String(): keybase1.TeamRole_NONE,
   429  				iName.String(): keybase1.TeamRole_NONE,
   430  			},
   431  			hidden: []string{aName.String()},
   432  		},
   433  		{
   434  			teamID:    cID,
   435  			requester: yank,
   436  			target:    tang, // in no teams after reset
   437  			expected: map[string]keybase1.TeamRole{
   438  				cName.String(): keybase1.TeamRole_NONE,
   439  				dName.String(): keybase1.TeamRole_NONE,
   440  				eName.String(): keybase1.TeamRole_NONE,
   441  				fName.String(): keybase1.TeamRole_NONE,
   442  				gName.String(): keybase1.TeamRole_NONE,
   443  				iName.String(): keybase1.TeamRole_NONE,
   444  			},
   445  			hidden: []string{aName.String()},
   446  		},
   447  		{
   448  			teamID:    dID,
   449  			requester: zulu,
   450  			target:    zulu,
   451  			expected: map[string]keybase1.TeamRole{
   452  				aName.String(): keybase1.TeamRole_ADMIN,
   453  				dName.String(): keybase1.TeamRole_NONE,
   454  				fName.String(): keybase1.TeamRole_NONE,
   455  			},
   456  			hidden: []string{cName.String()},
   457  		},
   458  		{
   459  			teamID:    dID,
   460  			requester: yank,
   461  			target:    zulu,
   462  			expected: map[string]keybase1.TeamRole{
   463  				cName.String(): keybase1.TeamRole_NONE,
   464  				dName.String(): keybase1.TeamRole_NONE,
   465  				fName.String(): keybase1.TeamRole_NONE,
   466  			},
   467  			hidden: []string{aName.String()},
   468  		},
   469  		{
   470  			teamID:    dID,
   471  			requester: xray,
   472  			target:    zulu,
   473  			expected: map[string]keybase1.TeamRole{
   474  				dName.String(): keybase1.TeamRole_NONE,
   475  				fName.String(): keybase1.TeamRole_NONE,
   476  			},
   477  			hidden: []string{aName.String(), cName.String()},
   478  		},
   479  		{
   480  			teamID:    dID,
   481  			requester: zulu,
   482  			target:    vict,
   483  			expected: map[string]keybase1.TeamRole{
   484  				aName.String(): keybase1.TeamRole_NONE,
   485  				dName.String(): keybase1.TeamRole_NONE,
   486  				fName.String(): keybase1.TeamRole_NONE,
   487  			},
   488  			hidden: []string{cName.String()},
   489  		},
   490  		{
   491  			teamID:    dID,
   492  			requester: yank,
   493  			target:    vict,
   494  			expected: map[string]keybase1.TeamRole{
   495  				cName.String(): keybase1.TeamRole_WRITER,
   496  				dName.String(): keybase1.TeamRole_NONE,
   497  				fName.String(): keybase1.TeamRole_NONE,
   498  			},
   499  			hidden: []string{aName.String()},
   500  		},
   501  		{
   502  			teamID:    dID,
   503  			requester: xray,
   504  			target:    vict,
   505  			expected: map[string]keybase1.TeamRole{
   506  				dName.String(): keybase1.TeamRole_NONE,
   507  				fName.String(): keybase1.TeamRole_NONE,
   508  			},
   509  			hidden: []string{aName.String(), cName.String()},
   510  		},
   511  		{
   512  			teamID:    dID,
   513  			requester: zulu,
   514  			target:    xray,
   515  			expected: map[string]keybase1.TeamRole{
   516  				aName.String(): keybase1.TeamRole_NONE,
   517  				dName.String(): keybase1.TeamRole_ADMIN,
   518  				fName.String(): keybase1.TeamRole_NONE,
   519  			},
   520  			hidden: []string{cName.String()},
   521  		},
   522  		{
   523  			teamID:    dID,
   524  			requester: yank,
   525  			target:    xray,
   526  			expected: map[string]keybase1.TeamRole{
   527  				cName.String(): keybase1.TeamRole_NONE,
   528  				dName.String(): keybase1.TeamRole_ADMIN,
   529  				fName.String(): keybase1.TeamRole_NONE,
   530  			},
   531  			hidden: []string{aName.String()},
   532  		},
   533  		{
   534  			teamID:    dID,
   535  			requester: xray,
   536  			target:    xray,
   537  			expected: map[string]keybase1.TeamRole{
   538  				dName.String(): keybase1.TeamRole_ADMIN,
   539  				fName.String(): keybase1.TeamRole_NONE,
   540  			},
   541  			hidden: []string{aName.String(), cName.String()},
   542  		},
   543  		{
   544  			teamID:    eID,
   545  			requester: yank,
   546  			target:    vict,
   547  			expected: map[string]keybase1.TeamRole{
   548  				cName.String(): keybase1.TeamRole_WRITER,
   549  				eName.String(): keybase1.TeamRole_WRITER,
   550  				gName.String(): keybase1.TeamRole_WRITER,
   551  				iName.String(): keybase1.TeamRole_NONE,
   552  			},
   553  			hidden: []string{aName.String()},
   554  		},
   555  		{
   556  			teamID:    eID,
   557  			requester: yank,
   558  			target:    xray,
   559  			expected: map[string]keybase1.TeamRole{
   560  				cName.String(): keybase1.TeamRole_NONE,
   561  				eName.String(): keybase1.TeamRole_NONE,
   562  				gName.String(): keybase1.TeamRole_NONE,
   563  				iName.String(): keybase1.TeamRole_NONE,
   564  			},
   565  			hidden: []string{aName.String()},
   566  		},
   567  		{
   568  			teamID:    gID,
   569  			requester: whis,
   570  			target:    whis,
   571  			expected: map[string]keybase1.TeamRole{
   572  				gName.String(): keybase1.TeamRole_ADMIN,
   573  			},
   574  			hidden: []string{aName.String(), cName.String(), eName.String()},
   575  		},
   576  		{
   577  			teamID:    gID,
   578  			requester: yank,
   579  			target:    whis,
   580  			expected: map[string]keybase1.TeamRole{
   581  				cName.String(): keybase1.TeamRole_NONE,
   582  				gName.String(): keybase1.TeamRole_ADMIN,
   583  			},
   584  			hidden: []string{aName.String(), eName.String()},
   585  		},
   586  		{
   587  			teamID:    gID,
   588  			requester: zulu,
   589  			target:    whis,
   590  			expected: map[string]keybase1.TeamRole{
   591  				aName.String(): keybase1.TeamRole_NONE,
   592  				gName.String(): keybase1.TeamRole_ADMIN,
   593  			},
   594  			hidden: []string{cName.String(), eName.String()},
   595  		},
   596  	}
   597  	for idx, tst := range tsts {
   598  		t.Logf("Testing testcase %d", idx)
   599  		name := fmt.Sprintf("happy/%d", idx)
   600  		t.Run(name, func(t *testing.T) {
   601  			mctx := libkb.NewMetaContextForTest(*tst.requester.tc)
   602  			results, err := loadTeamTree(t, mctx, tst.requester.notifications,
   603  				tst.teamID, tst.target.username, nil, nil)
   604  			require.NoError(t, err)
   605  			checkTeamTreeResults(t, tst.expected, nil, tst.hidden, results)
   606  		})
   607  	}
   608  
   609  	t.Logf("error path table testing")
   610  	errorTsts := []struct {
   611  		teamID            keybase1.TeamID
   612  		requester         *userPlusDevice
   613  		target            *userPlusDevice
   614  		failureTeamIDs    []keybase1.TeamID
   615  		failureTeamNames  []string
   616  		hidden            []string
   617  		expectedSuccesses map[string]keybase1.TeamRole
   618  	}{
   619  		{
   620  			teamID:           cID,
   621  			requester:        yank,
   622  			target:           whis,
   623  			failureTeamIDs:   []keybase1.TeamID{dID, gID},
   624  			failureTeamNames: []string{dName.String(), gName.String()},
   625  			expectedSuccesses: map[string]keybase1.TeamRole{
   626  				cName.String(): keybase1.TeamRole_NONE,
   627  				eName.String(): keybase1.TeamRole_NONE,
   628  				iName.String(): keybase1.TeamRole_NONE,
   629  			},
   630  			hidden: []string{aName.String()},
   631  		},
   632  		{
   633  			teamID:           cID,
   634  			requester:        yank,
   635  			target:           whis,
   636  			failureTeamIDs:   []keybase1.TeamID{iID},
   637  			failureTeamNames: []string{iName.String()},
   638  			expectedSuccesses: map[string]keybase1.TeamRole{
   639  				cName.String(): keybase1.TeamRole_NONE,
   640  				dName.String(): keybase1.TeamRole_NONE,
   641  				eName.String(): keybase1.TeamRole_NONE,
   642  				fName.String(): keybase1.TeamRole_NONE,
   643  				gName.String(): keybase1.TeamRole_ADMIN,
   644  			},
   645  			hidden: []string{aName.String()},
   646  		},
   647  		{
   648  			teamID:           eID,
   649  			requester:        yank,
   650  			target:           whis,
   651  			failureTeamIDs:   []keybase1.TeamID{cID},
   652  			failureTeamNames: []string{cName.String()},
   653  			expectedSuccesses: map[string]keybase1.TeamRole{
   654  				eName.String(): keybase1.TeamRole_NONE,
   655  				gName.String(): keybase1.TeamRole_ADMIN,
   656  				iName.String(): keybase1.TeamRole_NONE,
   657  			},
   658  			hidden: []string{},
   659  		},
   660  		{
   661  			teamID:           eID,
   662  			requester:        yank,
   663  			target:           whis,
   664  			failureTeamIDs:   []keybase1.TeamID{aID},
   665  			failureTeamNames: []string{aName.String()},
   666  			expectedSuccesses: map[string]keybase1.TeamRole{
   667  				cName.String(): keybase1.TeamRole_NONE,
   668  				eName.String(): keybase1.TeamRole_NONE,
   669  				gName.String(): keybase1.TeamRole_ADMIN,
   670  				iName.String(): keybase1.TeamRole_NONE,
   671  			},
   672  			hidden: []string{},
   673  		},
   674  		{
   675  			teamID:           eID,
   676  			requester:        yank,
   677  			target:           whis,
   678  			failureTeamIDs:   []keybase1.TeamID{aID, iID},
   679  			failureTeamNames: []string{aName.String(), iName.String()},
   680  			expectedSuccesses: map[string]keybase1.TeamRole{
   681  				cName.String(): keybase1.TeamRole_NONE,
   682  				eName.String(): keybase1.TeamRole_NONE,
   683  				gName.String(): keybase1.TeamRole_ADMIN,
   684  			},
   685  			hidden: []string{},
   686  		},
   687  		{
   688  			teamID:            eID,
   689  			requester:         yank,
   690  			target:            whis,
   691  			failureTeamIDs:    []keybase1.TeamID{eID},
   692  			failureTeamNames:  []string{eName.String()},
   693  			expectedSuccesses: map[string]keybase1.TeamRole{},
   694  			hidden:            []string{},
   695  		},
   696  	}
   697  	for idx, tst := range errorTsts {
   698  		t.Logf("Testing testcase %d", idx)
   699  		name := fmt.Sprintf("error/%d", idx)
   700  		t.Run(name, func(t *testing.T) {
   701  			mctx := libkb.NewMetaContextForTest(*tst.requester.tc)
   702  			results, err := loadTeamTree(t, mctx, tst.requester.notifications, tst.teamID,
   703  				tst.target.username, tst.failureTeamIDs, tst.failureTeamNames)
   704  			require.NoError(t, err)
   705  			checkTeamTreeResults(t, tst.expectedSuccesses, tst.failureTeamNames,
   706  				tst.hidden, results)
   707  		})
   708  	}
   709  
   710  	t.Logf("miscellaneous tests")
   711  	zuluMctx := libkb.NewMetaContextForTest(*zulu.tc)
   712  	victMctx := libkb.NewMetaContextForTest(*vict.tc)
   713  
   714  	_, err = loadTeamTree(t, zuluMctx, zulu.notifications, cID, unif.username, nil, nil)
   715  	require.IsType(t, libkb.NoKeyError{}, err, "cannot load a deleted user")
   716  
   717  	_, err = loadTeamTree(t, victMctx, vict.notifications, cID, yank.username, nil, nil)
   718  	require.IsType(t, teams.StubbedError{}, err, "can only load if you're an admin")
   719  }