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