e2e/gates_test.go
101 lines · 4562 bytes
1package e2e
2
3import (
4 "os"
5 "path/filepath"
6 "regexp"
7 "strings"
8 "testing"
9)
10
11// mr show reports the gates before the merge fails, mr merge names every
12// unmet gate at once, and mr review says when a verdict is advisory
13// (#199).
14func TestMergeGatesVisible(t *testing.T) {
15 t.Parallel()
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 for _, args := range [][]string{
24 {"repo", "create", "alice/svc"},
25 {"repo", "access", "grant", "alice/svc", "bob", "write"},
26 {"repo", "settings", "require-approvals", "alice/svc", "1"},
27 {"repo", "settings", "require-codeowners", "alice/svc", "on"},
28 {"repo", "settings", "require-resolved", "alice/svc", "on"},
29 } {
30 if _, errOut, code := inst.ssh(t, aliceKey, "", args...); code != 0 {
31 t.Fatalf("%v: %s", args, errOut)
32 }
33 }
34 env := inst.gitEnv(aliceKey)
35 work := t.TempDir()
36 mustGit(t, work, env, "clone", "-q", inst.sshURL("alice/svc"), "w")
37 dir := filepath.Join(work, "w")
38 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
39 os.WriteFile(filepath.Join(dir, "CODEOWNERS"), []byte("*.go @bob\n"), 0o644)
40 os.WriteFile(filepath.Join(dir, "svc.go"), []byte("package svc\n"), 0o644)
41 mustGit(t, dir, env, "add", ".")
42 mustGit(t, dir, env, "commit", "-q", "-m", "base")
43 mustGit(t, dir, env, "push", "-q", "origin", "main")
44 mustGit(t, dir, env, "checkout", "-q", "-b", "feat")
45 os.WriteFile(filepath.Join(dir, "svc.go"), []byte("package svc\n\nvar V = 1\n"), 0o644)
46 mustGit(t, dir, env, "commit", "-q", "-am", "change")
47 mustGit(t, dir, env, "push", "-q", "origin", "feat")
48 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "create", "alice/svc",
49 "--source", "feat", "--target", "main", "--title", "'change'"); code != 0 {
50 t.Fatalf("mr create: %s", errOut)
51 }
52
53 // Before anyone reviews: two unmet gates, visible on mr show.
54 out, _, _ := inst.ssh(t, aliceKey, "", "mr", "show", "alice/svc", "1", "--json")
55 for _, want := range []string{
56 `"approvals_required":1`, `"codeowners_required":true`, `"resolved_required":true`,
57 `"owners_outstanding":[{"files":["svc.go"],"owners":["bob"]}]`, `"fast_forward":true`,
58 `requires 1 fresh approval(s)`, `CODEOWNERS approval missing for: svc.go (owned by bob)`,
59 } {
60 if !strings.Contains(out, want) {
61 t.Errorf("mr show gates missing %s:\n%s", want, out)
62 }
63 }
64 // The merge names both at once.
65 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/svc", "1"); code != 4 ||
66 !strings.Contains(errOut, "fresh approval") || !strings.Contains(errOut, "CODEOWNERS approval missing") {
67 t.Fatalf("merge refusal does not name every gate: %d %s", code, errOut)
68 }
69
70 // A reader's approval is advisory, and says so when made.
71 out, _, code := inst.ssh(t, carolKey, "", "mr", "review", "alice/svc", "1", "--approve", "--json")
72 if code != 0 || !strings.Contains(out, `"counts":false`) {
73 t.Fatalf("reader review: %d %s", code, out)
74 }
75 if out, _, _ := inst.ssh(t, carolKey, "", "mr", "review", "alice/svc", "1", "--comment"); !strings.Contains(out, "advisory") {
76 t.Fatalf("reader review does not say it is advisory: %s", out)
77 }
78 out, _, _ = inst.ssh(t, aliceKey, "", "mr", "show", "alice/svc", "1", "--json")
79 if !strings.Contains(out, `requires 1 fresh approval(s)`) {
80 t.Fatalf("advisory approval counted in the gates:\n%s", out)
81 }
82
83 // The owner's approval meets both; the block says so and the merge lands.
84 out, _, _ = inst.ssh(t, bobKey, "", "mr", "review", "alice/svc", "1", "--approve", "--json")
85 if !strings.Contains(out, `"counts":true`) {
86 t.Fatalf("writer review: %s", out)
87 }
88 out, _, _ = inst.ssh(t, aliceKey, "", "mr", "show", "alice/svc", "1", "--json")
89 if strings.Contains(out, `"unmet"`) || !strings.Contains(out, `"approvals":["bob"]`) {
90 t.Fatalf("gates after approval:\n%s", out)
91 }
92 if out, _, _ := inst.ssh(t, aliceKey, "", "mr", "show", "alice/svc", "1"); !regexp.MustCompile(`gates\s+met; fast-forward possible`).MatchString(out) {
93 t.Fatalf("text gates line: %s", out)
94 }
95 if _, errOut, code := inst.ssh(t, aliceKey, "", "mr", "merge", "alice/svc", "1"); code != 0 {
96 t.Fatalf("merge: %s", errOut)
97 }
98 if out, _, _ := inst.ssh(t, aliceKey, "", "mr", "show", "alice/svc", "1", "--json"); strings.Contains(out, `"gates"`) {
99 t.Fatalf("gates reported on a merged request:\n%s", out)
100 }
101}