internal/control/checksgate_test.go
120 lines · 3928 bytes
1package control
2
3import (
4 "os"
5 "path/filepath"
6 "strings"
7 "testing"
8
9 "gitbay.org/gitbay/internal/store"
10)
11
12// gatesForHead builds a repository with require_checks on, a bare dir
13// holding the given .gitbay/ci.yml (empty string for none), and one MR
14// whose head carries no statuses at all. seed records a status on the
15// base commit, standing for a repository that reports from outside.
16func gatesForHead(t *testing.T, ciYML string) GatesOut {
17 return gatesForHeadSeeded(t, ciYML, false)
18}
19
20func gatesForHeadSeeded(t *testing.T, ciYML string, seed bool) GatesOut {
21 t.Helper()
22 st, repo, uid := newQueueTestRepo(t)
23 if _, err := st.UpdateRepoSettings(repo.ID, func(set *store.RepoSettings) { set.RequireChecks = true }); err != nil {
24 t.Fatal(err)
25 }
26 repo, err := st.RepoByID(repo.ID)
27 if err != nil {
28 t.Fatal(err)
29 }
30
31 git := gitRunner(t)
32 root := t.TempDir()
33 src := filepath.Join(root, "src")
34 os.MkdirAll(src, 0o755)
35 git(root, "init", "-q", "-b", "main", "src")
36 os.WriteFile(filepath.Join(src, "README"), []byte("x\n"), 0o644)
37 git(src, "add", ".")
38 git(src, "commit", "-q", "-m", "base")
39 targetSHA := strings.TrimSpace(git(src, "rev-parse", "HEAD"))
40 git(src, "checkout", "-q", "-b", "feature")
41 if ciYML != "" {
42 os.MkdirAll(filepath.Join(src, ".gitbay"), 0o755)
43 os.WriteFile(filepath.Join(src, ".gitbay", "ci.yml"), []byte(ciYML), 0o644)
44 }
45 os.WriteFile(filepath.Join(src, "README"), []byte("y\n"), 0o644)
46 git(src, "add", ".")
47 git(src, "commit", "-q", "-m", "change")
48 headSHA := strings.TrimSpace(git(src, "rev-parse", "HEAD"))
49
50 dir := RepoDir(root, repo.OwnerName, repo.Name)
51 os.MkdirAll(filepath.Dir(dir), 0o755)
52 git(root, "clone", "-q", "--bare", src, dir)
53
54 if _, err := st.CreateMR(repo.ID, uid, repo.ID, "feature", "main", "t", "", headSHA, "md", false); err != nil {
55 t.Fatal(err)
56 }
57 mr, err := st.MRByNumber(repo.ID, 1)
58 if err != nil {
59 t.Fatal(err)
60 }
61
62 if seed {
63 if err := st.SetCommitStatus(repo.ID, targetSHA, "lint", "success", "", "", uid); err != nil {
64 t.Fatal(err)
65 }
66 }
67
68 g, err := MergeGates(st, repo, mr, dir, targetSHA, headSHA)
69 if err != nil {
70 t.Fatal(err)
71 }
72 return g
73}
74
75func checksUnmet(g GatesOut) bool {
76 for _, u := range g.Unmet {
77 if strings.Contains(u, "green checks") {
78 return true
79 }
80 }
81 return false
82}
83
84// A repository with require_checks on and no CI configuration at its head
85// can never report a status, so the gate has nothing to wait for and the
86// merge must go through rather than be refused forever.
87func TestRequireChecksPassesWithNoCIConfig(t *testing.T) {
88 if g := gatesForHead(t, ""); checksUnmet(g) {
89 t.Fatalf("refused a head with no CI configuration: %v", g.Unmet)
90 }
91}
92
93// Jobs that only run on a schedule or on tags never report on a merge
94// request head either.
95func TestRequireChecksPassesWithNoPushJobs(t *testing.T) {
96 cfg := "jobs:\n nightly:\n schedule: \"0 3 * * *\"\n steps:\n - echo hi\n" +
97 " release:\n tags: \"v*\"\n steps:\n - echo hi\n"
98 if g := gatesForHead(t, cfg); checksUnmet(g) {
99 t.Fatalf("refused a head whose jobs never run on a push: %v", g.Unmet)
100 }
101}
102
103// A push job at the head should have reported something. Silence there
104// means CI did not run, which is what the gate is for.
105func TestRequireChecksRefusesSilentPushJob(t *testing.T) {
106 cfg := "jobs:\n unit:\n steps:\n - echo hi\n"
107 if g := gatesForHead(t, cfg); !checksUnmet(g) {
108 t.Fatalf("allowed a head whose push job reported nothing: %v", g.Unmet)
109 }
110}
111
112// A repository whose checks come from outside — `status set`, no
113// .gitbay/ci.yml — looks like one with no CI at all. Having reported
114// before is what says a report was coming, so a silent head there is
115// still refused.
116func TestRequireChecksRefusesSilentHeadInReportingRepo(t *testing.T) {
117 if g := gatesForHeadSeeded(t, "", true); !checksUnmet(g) {
118 t.Fatalf("allowed a silent head in a repository that reports statuses: %v", g.Unmet)
119 }
120}