1package systests
2
3import (
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
17func 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
23func 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
33func 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
55loop:
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
87func 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
119type mockConverter struct {
120	failureTeamIDs []keybase1.TeamID
121	loader         *teams.Treeloader
122}
123
124func (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
134func newMockConverter(failureTeamIDs []keybase1.TeamID, loader *teams.Treeloader) mockConverter {
135	return mockConverter{
136		failureTeamIDs: failureTeamIDs,
137		loader:         loader,
138	}
139}
140
141func 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}
720