e2e/teams_test.go
120 lines · 5222 bytes
1package e2e
2
3import (
4 "strings"
5 "testing"
6)
7
8func TestOrgTeams(t *testing.T) {
9 t.Parallel()
10 inst := startInstance(t)
11 adminKey := inst.newKey(t, "alice")
12 bobKey := inst.newKey(t, "bob")
13 carolKey := inst.newKey(t, "carol")
14 eveKey := inst.newKey(t, "eve")
15 inst.admin(t, "admin", "user", "create", "alice", "--key", adminKey+".pub")
16 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
17 inst.admin(t, "admin", "user", "create", "carol", "--key", carolKey+".pub")
18 inst.admin(t, "admin", "user", "create", "eve", "--key", eveKey+".pub")
19
20 // Org with two private repos; bob and carol are plain members.
21 for _, args := range [][]string{
22 {"org", "create", "acme"},
23 {"org", "members", "add", "acme", "bob"},
24 {"org", "members", "add", "acme", "carol"},
25 {"repo", "create", "acme/core", "--private"},
26 {"repo", "create", "acme/site", "--private"},
27 } {
28 if _, errOut, code := inst.ssh(t, adminKey, "", args...); code != 0 {
29 t.Fatalf("%v: %s", args, errOut)
30 }
31 }
32
33 // Degenerate case: plain membership implies write everywhere.
34 if _, _, code := inst.ssh(t, bobKey, "", "issue", "create", "acme/core", "--title", "'pre'"); code != 0 {
35 t.Fatal("member write lost (degenerate case broken)")
36 }
37
38 // Scope the org: members get nothing by default, teams grant.
39 if _, _, code := inst.ssh(t, bobKey, "", "org", "settings", "members-role", "acme", "none"); code != 4 {
40 t.Fatal("non-admin changed members-role")
41 }
42 if _, _, code := inst.ssh(t, adminKey, "", "org", "settings", "members-role", "acme", "none"); code != 0 {
43 t.Fatal("members-role failed")
44 }
45 // bob now cannot even see the private repo.
46 if _, _, code := inst.ssh(t, bobKey, "", "repo", "show", "acme/core"); code != 3 {
47 t.Fatal("scoped member still sees private repo")
48 }
49
50 // Team "core-devs": bob gets write on core, read on site.
51 if _, _, code := inst.ssh(t, adminKey, "", "org", "team", "create", "acme", "core-devs"); code != 0 {
52 t.Fatal("team create failed")
53 }
54 if _, errOut, code := inst.ssh(t, adminKey, "", "org", "team", "add", "acme", "core-devs", "eve"); code != 2 || !strings.Contains(errOut, "not a member") {
55 t.Fatalf("non-member added to team: %d %s", code, errOut)
56 }
57 if _, _, code := inst.ssh(t, adminKey, "", "org", "team", "add", "acme", "core-devs", "bob"); code != 0 {
58 t.Fatal("team add failed")
59 }
60 if _, _, code := inst.ssh(t, adminKey, "", "org", "team", "grant", "acme", "core-devs", "acme/core", "write"); code != 0 {
61 t.Fatal("team grant failed")
62 }
63 if _, _, code := inst.ssh(t, adminKey, "", "org", "team", "grant", "acme", "core-devs", "acme/site", "read"); code != 0 {
64 t.Fatal("second grant failed")
65 }
66 // Grants are limited to the org's own repos.
67 if _, _, code := inst.ssh(t, adminKey, "", "repo", "create", "alice/own"); code != 0 {
68 t.Fatal("repo create failed")
69 }
70 if _, errOut, code := inst.ssh(t, adminKey, "", "org", "team", "grant", "acme", "core-devs", "alice/own", "read"); code != 2 || !strings.Contains(errOut, "own org") {
71 t.Fatalf("cross-org grant allowed: %d %s", code, errOut)
72 }
73
74 // bob: write on core (can open issues), read-only on site (visible,
75 // not writable). carol (no team): nothing.
76 if _, _, code := inst.ssh(t, bobKey, "", "issue", "create", "acme/core", "--title", "'works'"); code != 0 {
77 t.Fatal("team write not effective")
78 }
79 if _, _, code := inst.ssh(t, bobKey, "", "repo", "show", "acme/site"); code != 0 {
80 t.Fatal("team read not effective")
81 }
82 if _, _, code := inst.ssh(t, bobKey, "", "repo", "settings", "protect", "acme/site", "main"); code != 4 {
83 t.Fatal("read grant allowed admin action")
84 }
85 if _, _, code := inst.ssh(t, carolKey, "", "repo", "show", "acme/core"); code != 3 {
86 t.Fatal("teamless member sees scoped repo")
87 }
88 out, _, _ := inst.ssh(t, bobKey, "", "repo", "list")
89 if !strings.Contains(out, "acme/core") || !strings.Contains(out, "acme/site") {
90 t.Fatalf("team repos missing from listing: %s", out)
91 }
92
93 // show reflects members and grants; member removal drops access.
94 out, _, _ = inst.ssh(t, adminKey, "", "org", "team", "show", "acme", "core-devs", "--json")
95 if !strings.Contains(out, `"members":["bob"]`) || !strings.Contains(out, `"repo":"acme/core","role":"write"`) {
96 t.Fatalf("team show: %s", out)
97 }
98 if _, _, code := inst.ssh(t, adminKey, "", "org", "team", "remove", "acme", "core-devs", "bob"); code != 0 {
99 t.Fatal("team remove failed")
100 }
101 if _, _, code := inst.ssh(t, bobKey, "", "repo", "show", "acme/core"); code != 3 {
102 t.Fatal("removed member kept access")
103 }
104
105 // Deleting the team cascades its grants.
106 inst.ssh(t, adminKey, "", "org", "team", "add", "acme", "core-devs", "carol")
107 if _, _, code := inst.ssh(t, carolKey, "", "repo", "show", "acme/core"); code != 0 {
108 t.Fatal("carol team access missing")
109 }
110 if _, _, code := inst.ssh(t, adminKey, "", "org", "team", "delete", "acme", "core-devs"); code != 0 {
111 t.Fatal("team delete failed")
112 }
113 if _, _, code := inst.ssh(t, carolKey, "", "repo", "show", "acme/core"); code != 3 {
114 t.Fatal("deleted team's grant survived")
115 }
116 // Org admins keep admin regardless of scoping.
117 if _, _, code := inst.ssh(t, adminKey, "", "repo", "settings", "protect", "acme/core", "main"); code != 0 {
118 t.Fatal("org admin lost access")
119 }
120}