e2e/orglabels_test.go
119 lines · 5595 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10// An org's labels and milestones reach every repository under it; a
11// commit in one repository closes an issue in another; the org pages
12// answer members and outsiders as their access allows.
13func TestOrgLabelsMilestonesAndCrossRepoCloses(t *testing.T) {
14 t.Parallel()
15 inst := startInstance(t)
16 aliceKey := inst.newKey(t, "alice")
17 carolKey := inst.newKey(t, "carol")
18 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
19 inst.admin(t, "admin", "user", "create", "carol", "--key", carolKey+".pub", "--email", "carol@example.test", "--verified")
20 must := func(key string, args ...string) string {
21 t.Helper()
22 out, errOut, code := inst.ssh(t, key, "", args...)
23 if code != 0 {
24 t.Fatalf("%v: exit %d %s", args, code, errOut)
25 }
26 return out
27 }
28 must(aliceKey, "org", "create", "acme")
29 must(aliceKey, "repo", "create", "acme/lib")
30 must(aliceKey, "repo", "create", "acme/widget", "--private")
31 must(aliceKey, "issue", "create", "acme/lib", "--title", "'lib one'")
32 must(aliceKey, "issue", "create", "acme/widget", "--title", "'widget one'")
33
34 // Repo labels in both, then the org set folds them in.
35 must(aliceKey, "issue", "label", "acme/lib", "1", "--add", "bug")
36 must(aliceKey, "issue", "label", "acme/widget", "1", "--add", "bug")
37 out := must(aliceKey, "org", "label", "set", "acme", "bug", "--color", "ff0000", "--json")
38 if !strings.Contains(out, `"folded":2`) {
39 t.Fatalf("org label set: %s", out)
40 }
41 out = must(aliceKey, "label", "list", "acme/lib", "--json")
42 if !strings.Contains(out, `"org":true`) || !strings.Contains(out, `"issues":2`) {
43 t.Fatalf("lib label list: %s", out)
44 }
45 if _, errOut, code := inst.ssh(t, aliceKey, "", "label", "set", "acme/lib", "bug"); code == 0 || !strings.Contains(errOut, "org label set acme bug") {
46 t.Fatalf("repo label set over org name: exit %d %s", code, errOut)
47 }
48
49 // An org milestone attaches from both repositories and counts across.
50 must(aliceKey, "org", "milestone", "create", "acme", "v1", "--due", "2027-01-01")
51 must(aliceKey, "issue", "milestone", "acme/lib", "1", "v1")
52 must(aliceKey, "issue", "milestone", "acme/widget", "1", "v1")
53 out = must(aliceKey, "org", "milestone", "list", "acme", "--json")
54 if !strings.Contains(out, `"open":2`) {
55 t.Fatalf("org milestone list: %s", out)
56 }
57 out = must(aliceKey, "issue", "list", "acme/lib", "--milestone", "v1", "--json")
58 if !strings.Contains(out, `"number":1`) {
59 t.Fatalf("issue list filtered by org milestone: %s", out)
60 }
61
62 // A push to acme/lib closes acme/widget#1 and leaves a comment there.
63 work := t.TempDir()
64 env := inst.gitEnv(aliceKey)
65 mustGit(t, work, env, "clone", inst.sshURL("acme/lib"), "w")
66 dir := filepath.Join(work, "w")
67 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
68 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
69 mustGit(t, dir, env, "add", ".")
70 mustGit(t, dir, env, "commit", "-q", "-m", "fix the widget\n\nCloses acme/widget#1")
71 mustGit(t, dir, env, "push", "-q", "origin", "main")
72 out = must(aliceKey, "issue", "show", "acme/widget", "1", "--json")
73 if !strings.Contains(out, `"state":"closed"`) || !strings.Contains(out, "](/acme/lib/commit/") {
74 t.Fatalf("widget#1 after cross-repo close: %s", out)
75 }
76 out = must(aliceKey, "org", "milestone", "list", "acme", "--json")
77 if !strings.Contains(out, `"open":1`) || !strings.Contains(out, `"closed":1`) {
78 t.Fatalf("org milestone progress after close: %s", out)
79 }
80
81 // carol is outside: her counts stop at acme/lib, the public one. The
82 // page checks below are anonymous, which acme/lib being public allows
83 // just the same.
84 out = must(carolKey, "org", "milestone", "list", "acme", "--json")
85 if !strings.Contains(out, `"open":1`) || !strings.Contains(out, `"closed":0`) {
86 t.Fatalf("outsider progress: %s", out)
87 }
88 if status, body := inst.get(t, "/acme/-/labels"); status != 200 || !strings.Contains(body, ">bug<") {
89 t.Fatalf("org labels page: %d", status)
90 }
91 if status, body := inst.get(t, "/acme/-/milestones"); status != 200 || !strings.Contains(body, "v1") || !strings.Contains(body, "0 closed, 1 open") {
92 t.Fatalf("org milestones page: %d\n%s", status, body)
93 }
94 if status, body := inst.get(t, "/acme/lib/labels"); status != 200 || !strings.Contains(body, `chip-neutral">org<`) {
95 t.Fatalf("repo labels page lacks the org mark: %d", status)
96 }
97 // carol cannot close into the private repo from a repo she owns.
98 must(carolKey, "repo", "create", "carol/own")
99 must(aliceKey, "issue", "create", "acme/widget", "--title", "'widget two'")
100 cwork := t.TempDir()
101 cenv := inst.gitEnv(carolKey)
102 mustGit(t, cwork, cenv, "clone", inst.sshURL("carol/own"), "w")
103 cdir := filepath.Join(cwork, "w")
104 os.WriteFile(filepath.Join(cdir, "a.txt"), []byte("a\n"), 0o644)
105 mustGit(t, cdir, cenv, "checkout", "-q", "-b", "main")
106 mustGit(t, cdir, cenv, "add", ".")
107 mustGit(t, cdir, cenv, "commit", "-q", "-m", "sneaky\n\nCloses acme/widget#2")
108 mustGit(t, cdir, cenv, "push", "-q", "origin", "main")
109 out = must(aliceKey, "issue", "show", "acme/widget", "2", "--json")
110 if !strings.Contains(out, `"state":"open"`) || strings.Contains(out, "sneaky") {
111 t.Fatalf("outsider acted on a private repo's issue: %s", out)
112 }
113 // With the public repo gone private, the org pages are not found for
114 // an anonymous reader.
115 must(aliceKey, "repo", "settings", "visibility", "acme/lib", "private")
116 if status, _ := inst.get(t, "/acme/-/labels"); status != 404 {
117 t.Fatalf("private org labels page for anonymous: %d", status)
118 }
119}