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