Commit ba9cd01976
Verified · cmc
e2e/orglabels_test.go added +117
| @@ -0,0 +1,117 @@ | ||
| 1 | package e2e | |
| 2 | ||
| 3 | import ( | |
| 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. | |
| 13 | func 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: she reads the org pages because acme/lib is public, | |
| 81 | // and the counts stop at it. | |
| 82 | out = must(carolKey, "org", "milestone", "list", "acme", "--json") | |
| 83 | if !strings.Contains(out, `"open":1`) || !strings.Contains(out, `"closed":0`) { | |
| 84 | t.Fatalf("outsider progress: %s", out) | |
| 85 | } | |
| 86 | if status, body := inst.get(t, "/acme/-/labels"); status != 200 || !strings.Contains(body, ">bug<") { | |
| 87 | t.Fatalf("org labels page: %d", status) | |
| 88 | } | |
| 89 | if status, body := inst.get(t, "/acme/-/milestones"); status != 200 || !strings.Contains(body, "v1") || !strings.Contains(body, "0 closed, 1 open") { | |
| 90 | t.Fatalf("org milestones page: %d\n%s", status, body) | |
| 91 | } | |
| 92 | if status, body := inst.get(t, "/acme/lib/labels"); status != 200 || !strings.Contains(body, `chip-neutral">org<`) { | |
| 93 | t.Fatalf("repo labels page lacks the org mark: %d", status) | |
| 94 | } | |
| 95 | // carol cannot close into the private repo from a repo she owns. | |
| 96 | must(carolKey, "repo", "create", "carol/own") | |
| 97 | must(aliceKey, "issue", "create", "acme/widget", "--title", "'widget two'") | |
| 98 | cwork := t.TempDir() | |
| 99 | cenv := inst.gitEnv(carolKey) | |
| 100 | mustGit(t, cwork, cenv, "clone", inst.sshURL("carol/own"), "w") | |
| 101 | cdir := filepath.Join(cwork, "w") | |
| 102 | os.WriteFile(filepath.Join(cdir, "a.txt"), []byte("a\n"), 0o644) | |
| 103 | mustGit(t, cdir, cenv, "checkout", "-q", "-b", "main") | |
| 104 | mustGit(t, cdir, cenv, "add", ".") | |
| 105 | mustGit(t, cdir, cenv, "commit", "-q", "-m", "sneaky\n\nCloses acme/widget#2") | |
| 106 | mustGit(t, cdir, cenv, "push", "-q", "origin", "main") | |
| 107 | out = must(aliceKey, "issue", "show", "acme/widget", "2", "--json") | |
| 108 | if !strings.Contains(out, `"state":"open"`) || strings.Contains(out, "sneaky") { | |
| 109 | t.Fatalf("outsider acted on a private repo's issue: %s", out) | |
| 110 | } | |
| 111 | // With the public repo gone private, the org pages are not found for | |
| 112 | // an anonymous reader. | |
| 113 | must(aliceKey, "repo", "settings", "visibility", "acme/lib", "private") | |
| 114 | if status, _ := inst.get(t, "/acme/-/labels"); status != 404 { | |
| 115 | t.Fatalf("private org labels page for anonymous: %d", status) | |
| 116 | } | |
| 117 | } | |