internal/control/checksgate_test.go

4e7f0573d0c35b2ec41de44f17f8fe45d3cd2408
gitbay/internal/control/checksgate_test.go history · blame · raw

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}