e2e/reviewrequest_test.go
128 lines · 5627 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8)
9
10// TestMRReviewRequest drives "mr review request" (#145) end to end: a
11// requested reviewer reaches the queue without being otherwise involved,
12// drops out once they review the current head, comes back on a new push,
13// and --remove takes them out outright. A separate, private repository
14// checks that requesting someone who cannot read it is refused.
15func TestMRReviewRequest(t *testing.T) {
16 inst := startInstance(t)
17 aliceKey := inst.newKey(t, "alice")
18 bobKey := inst.newKey(t, "bob")
19 carolKey := inst.newKey(t, "carol")
20 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
21 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
22 inst.admin(t, "admin", "user", "create", "carol", "--key", carolKey+".pub")
23
24 // Public, and bob is granted nothing: any involvement he has in the
25 // queue can only come from being asked directly.
26 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
27 t.Fatalf("repo create: %s", errOut)
28 }
29
30 env := inst.gitEnv(aliceKey)
31 work := t.TempDir()
32 mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
33 dir := filepath.Join(work, "w")
34 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\n"), 0o644)
35 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
36 mustGit(t, dir, env, "add", ".")
37 mustGit(t, dir, env, "commit", "-q", "-m", "base")
38 mustGit(t, dir, env, "push", "-q", "origin", "main")
39 mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
40 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\nb\n"), 0o644)
41 mustGit(t, dir, env, "add", ".")
42 mustGit(t, dir, env, "commit", "-q", "-m", "feat")
43 mustGit(t, dir, env, "push", "-q", "origin", "feat")
44
45 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/app",
46 "--source", "feat", "--target", "main", "--title", "'add b'", "--draft"); code != 0 {
47 t.Fatalf("mr create: %s", errOut)
48 }
49
50 // Asking while a draft does not put it in bob's queue: draft merge
51 // requests stay out of the review queue for everyone, requested or
52 // not.
53 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "review", "request", "alice/app", "1", "--add", "bob"); code != 0 {
54 t.Fatalf("review request: %s", errOut)
55 }
56 if q := reviewQueue(t, inst, bobKey); len(q) != 0 {
57 t.Fatalf("a draft is waiting on the requested reviewer: %v", q)
58 }
59
60 // Ready: the request now surfaces, and it notified bob at the same
61 // moment it notified everyone else.
62 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "ready", "alice/app", "1"); code != 0 {
63 t.Fatalf("mr ready: %s", errOut)
64 }
65 if !strings.Contains(inbox(t, inst, bobKey), "ready for review") {
66 t.Fatalf("requested reviewer not notified by mr ready:\n%s", inbox(t, inst, bobKey))
67 }
68 if q := reviewQueue(t, inst, bobKey); len(q) != 1 || q[0] != 1 {
69 t.Fatalf("requested reviewer not in queue: %v", q)
70 }
71
72 // Reviewing the current head empties the queue, the same rule an
73 // involved reviewer follows.
74 if _, errOut, code := inst.ssh(t, bobKey, "", "mr", "review", "alice/app", "1", "--approve"); code != 0 {
75 t.Fatalf("review: %s", errOut)
76 }
77 if q := reviewQueue(t, inst, bobKey); len(q) != 0 {
78 t.Fatalf("queue did not empty after reviewing the head: %v", q)
79 }
80
81 // A new push moves the head, so the review no longer covers it: back
82 // in the queue.
83 os.WriteFile(filepath.Join(dir, "a.txt"), []byte("a\nb\nc\n"), 0o644)
84 mustGit(t, dir, env, "add", ".")
85 mustGit(t, dir, env, "commit", "-q", "-m", "more")
86 mustGit(t, dir, env, "push", "-q", "origin", "feat")
87 if q := reviewQueue(t, inst, bobKey); len(q) != 1 || q[0] != 1 {
88 t.Fatalf("new head did not bring the request back: %v", q)
89 }
90
91 // --remove takes it out outright.
92 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "review", "request", "alice/app", "1", "--remove", "bob"); code != 0 {
93 t.Fatalf("review request --remove: %s", errOut)
94 }
95 if q := reviewQueue(t, inst, bobKey); len(q) != 0 {
96 t.Fatalf("queue after --remove: %v", q)
97 }
98 if _, _, code := inst.ssh(t, aliceKey, "", "mr", "review", "request", "alice/app", "1", "--remove", "bob"); code != 3 {
99 t.Fatalf("removing an absent reviewer should 404, got %d", code)
100 }
101
102 // A private repository where carol has no access at all: asking her
103 // for a review is refused, not silently recorded.
104 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/secret", "--private"); code != 0 {
105 t.Fatalf("repo create: %s", errOut)
106 }
107 work2 := t.TempDir()
108 mustGit(t, work2, env, "clone", inst.sshURL("alice/secret"), "w")
109 dir2 := filepath.Join(work2, "w")
110 os.WriteFile(filepath.Join(dir2, "a.txt"), []byte("a\n"), 0o644)
111 mustGit(t, dir2, env, "checkout", "-q", "-b", "main")
112 mustGit(t, dir2, env, "add", ".")
113 mustGit(t, dir2, env, "commit", "-q", "-m", "base")
114 mustGit(t, dir2, env, "push", "-q", "origin", "main")
115 mustGit(t, dir2, env, "checkout", "-q", "-b", "feat")
116 os.WriteFile(filepath.Join(dir2, "a.txt"), []byte("a\nb\n"), 0o644)
117 mustGit(t, dir2, env, "add", ".")
118 mustGit(t, dir2, env, "commit", "-q", "-m", "feat")
119 mustGit(t, dir2, env, "push", "-q", "origin", "feat")
120 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/secret",
121 "--source", "feat", "--target", "main", "--title", "'private'"); code != 0 {
122 t.Fatalf("mr create: %s", errOut)
123 }
124 _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "review", "request", "alice/secret", "1", "--add", "carol")
125 if code != 4 || !strings.Contains(errOut, "cannot read") {
126 t.Fatalf("requesting a review from someone with no access should be refused: exit %d, %s", code, errOut)
127 }
128}