package e2e import ( "context" "os/exec" "strings" "testing" "github.com/barerepo/server/internal/token" ) // Chapter 37.4: require_runs names the runs the default branch waits for, and it was never read. func TestTheDefaultBranchWaitsForTheRunsTheConfigNames(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") lisa := in.account("lisa") work := seed(t, in, john, "john", "johnbot") write(t, work, ".barerepo/config", "[repo]\nvisibility = \"public\"\n\n"+ "[access]\npush = [\"lisa\"]\n\n[proposals]\nrequire_runs = [\"build\"]\n") run(t, work, "git", "add", "-A") run(t, work, "git", "commit", "-qm", "builds must pass before master takes a commit") run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master") hers := clone(t, in, lisa, "/john/johnbot") commit(t, hers, "package main\n\nfunc main() {}\n", "a change nobody built") out, err := try(t, hers, "git", "push", in.url(lisa, "/john/johnbot"), "master") if err == nil { t.Fatalf("master took a commit with no passing build:\n%s", out) } for _, want := range []string{"require_runs", "build", "refs/proposals/new"} { if !strings.Contains(out, want) { t.Errorf("the rejection does not mention %q:\n%s", want, out) } } // The same commit, once a run named build has passed on it, is the push that must go through. sha := strings.TrimSpace(run(t, hers, "git", "rev-parse", "HEAD")) passRun(t, in, "john", "john/johnbot", sha, "build") if code, _, body := get(t, in.http.URL+"/john/johnbot/run/"+sha); code != 200 { t.Fatalf("the run page answered %d", code) } else { t.Logf("RUNPAGE %.900s", body[strings.Index(body, "class=\"card\""):]) } if out, err := try(t, hers, "git", "push", in.url(lisa, "/john/johnbot"), "master"); err != nil { t.Errorf("master refused a commit whose build passed (sha %s): %v\n%s", sha, err, out) } } // The owner is exempt, or a repository whose runner is gone has nobody who can fix the config. func TestTheOwnerIsNotLockedOutByARunThatCannotHappen(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") work := seed(t, in, john, "john", "johnbot") write(t, work, ".barerepo/config", "[proposals]\nrequire_runs = [\"build\"]\n") run(t, work, "git", "add", "-A") run(t, work, "git", "commit", "-qm", "require a run no runner will ever take") run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master") write(t, work, "conn.go", "package irc\n") run(t, work, "git", "add", "-A") run(t, work, "git", "commit", "-qm", "the owner undoing their own rule") if out, err := try(t, work, "git", "push", in.url(john, "/john/johnbot"), "master"); err != nil { t.Errorf("the owner cannot reach their own repository to fix it: %v\n%s", err, out) } } // passRun records one passing run under a name, the way a runner finishing a job does. func passRun(t *testing.T, in *instance, account, repo, sha, name string) { t.Helper() ctx := context.Background() runnerToken, tok, err := in.db.CreateToken(ctx, token.Runner, account, repo, "a runner") if err != nil { t.Fatal(err) } runner, err := in.db.AttachRunner(ctx, tok.ID, repo, "uproar.local", "linux", "amd64", nil) if err != nil { t.Fatal(err) } if _, err := in.db.QueueJobFor(ctx, repo, "refs/heads/master", sha, "go build", "", nil, name); err != nil { t.Fatal(err) } job, err := in.db.TakeJob(ctx, repo, *runner) if err != nil || job == nil { t.Fatalf("the runner could not take the job: %v", err) } finish(t, in, runnerToken, job.ID, 0) } // Chapter 33.6 is one recipe: edit default_branch, commit, push. It was parsed and read by nothing. func TestChangingTheDefaultBranchInTheConfigMovesIt(t *testing.T) { if _, err := exec.LookPath("git"); err != nil { t.Skip("git is not installed") } in := newInstance(t) john := in.account("john") work := seed(t, in, john, "john", "johnbot") run(t, work, "git", "branch", "main") run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "main") write(t, work, ".barerepo/config", "[repo]\ndefault_branch = \"main\"\nvisibility = \"public\"\n") run(t, work, "git", "add", "-A") run(t, work, "git", "commit", "-qm", "move to main") run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master") // The config arrives on master, so main has to catch up before it can hold the file. run(t, work, "git", "push", "-q", in.url(john, "/john/johnbot"), "master:main") // A fresh clone takes its branch from HEAD, which is the only place this setting can show. fresh := clone(t, in, john, "/john/johnbot") if got := strings.TrimSpace(run(t, fresh, "git", "rev-parse", "--abbrev-ref", "HEAD")); got != "main" { t.Errorf("a clone landed on %q, and the config says main", got) } // And the pages agree, or a reader is looking at a branch nobody else is on. _, _, body := get(t, in.http.URL+"/john/johnbot") if !strings.Contains(body, ">main") { t.Errorf("the landing page does not show main as the branch:\n%s", body[:min(len(body), 1200)]) } }