e2e/buildcancelweb_test.go

v1.14.0
gitbay/e2e/buildcancelweb_test.go history · blame · raw

124 lines · 5319 bytes

  1package e2e
  2
  3import (
  4	"net/url"
  5	"os"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9)
 10
 11// TestBuildCancelWeb covers cancelling a build from its page (#162). The
 12// control is offered for anything the command accepts — queued or running —
 13// and hidden once a build reaches a terminal state; the command decides for
 14// real, so a stale or repeated post against a build that is no longer
 15// cancellable shows the refusal rather than a broken page.
 16func TestBuildCancelWeb(t *testing.T) {
 17	inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n")
 18	aliceKey := inst.newKey(t, "alice")
 19	inst.admin(t, "admin", "user", "create", "alice",
 20		"--key", aliceKey+".pub", "--email", "alice@example.test", "--verified")
 21
 22	// ci is an ordinary account; its runner key is self-added with
 23	// --scope runner, which confines it to the runner protocol and
 24	// read-only git rather than reaching for admin.
 25	ciKey := inst.newKey(t, "ci")
 26	inst.admin(t, "admin", "user", "create", "ci", "--key", ciKey+".pub")
 27	runnerKey := inst.newKey(t, "ci-runner")
 28	pub, _ := os.ReadFile(runnerKey + ".pub")
 29	if _, errOut, code := inst.ssh(t, ciKey, string(pub), "keys", "add", "--scope", "runner"); code != 0 {
 30		t.Fatalf("keys add --scope runner: %s", errOut)
 31	}
 32	if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
 33		t.Fatalf("repo create: %s", errOut)
 34	}
 35	work := t.TempDir()
 36	env := inst.gitEnv(aliceKey)
 37	mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
 38	dir := filepath.Join(work, "w")
 39	os.MkdirAll(filepath.Join(dir, ".gitbay"), 0o755)
 40	os.WriteFile(filepath.Join(dir, ".gitbay", "ci.yml"), []byte("jobs:\n  unit:\n    steps:\n      - echo fine\n"), 0o644)
 41	mustGit(t, dir, env, "checkout", "-q", "-b", "main")
 42	mustGit(t, dir, env, "add", ".")
 43	mustGit(t, dir, env, "commit", "-q", "-m", "ci")
 44	mustGit(t, dir, env, "push", "-q", "origin", "main")
 45
 46	alice := inst.login(t, aliceKey)
 47	build1 := inst.base() + "/alice/app/builds/1"
 48
 49	// Build 1 is queued: the control is on the page, for someone with
 50	// write access.
 51	_, body := browserGet(t, alice, build1)
 52	if !strings.Contains(body, `action="/alice/app/builds/1/cancel"`) {
 53		t.Fatalf("no cancel control on a queued build:\n%s", body)
 54	}
 55	// A reader gets no control.
 56	_, anon := browserGet(t, newBrowser(t), build1)
 57	if strings.Contains(anon, "/cancel") {
 58		t.Fatal("anonymous visitor sees the cancel control")
 59	}
 60
 61	// Cancelling from the page lands where the CLI sees it.
 62	if status, _ := browserPost(t, alice, build1+"/cancel", url.Values{}); status != 200 {
 63		t.Fatalf("cancel post: %d", status)
 64	}
 65	if out, _, _ := inst.ssh(t, aliceKey, "", "build", "list", "alice/app"); !strings.Contains(out, "unit\tcancelled") {
 66		t.Fatalf("build not cancelled: %s", out)
 67	}
 68	// Cancelled, so the control is gone.
 69	if _, body = browserGet(t, alice, build1); strings.Contains(body, "/cancel") {
 70		t.Fatalf("cancel control still on a cancelled build:\n%s", body)
 71	}
 72
 73	// Build 2: queued, then claimed by a runner without one actually
 74	// running any steps — enough to move it to "running".
 75	if _, errOut, code := inst.ssh(t, aliceKey, "", "build", "trigger", "alice/app", "unit"); code != 0 {
 76		t.Fatalf("trigger: %s", errOut)
 77	}
 78	if out, _, code := inst.ssh(t, runnerKey, "", "runner", "next", "alice/app"); code != 0 || strings.Contains(out, "no pending") {
 79		t.Fatalf("runner claim: %s", out)
 80	}
 81	build2 := inst.base() + "/alice/app/builds/2"
 82	_, body = browserGet(t, alice, build2)
 83	if !strings.Contains(body, "running") {
 84		t.Fatalf("build 2 not running:\n%s", body)
 85	}
 86	// The command accepts cancelling a running build too — that is the
 87	// case #162 was filed for, a run going nowhere — so the control stays
 88	// up, worded so cancelling does not read as instant.
 89	if !strings.Contains(body, `action="/alice/app/builds/2/cancel"`) {
 90		t.Fatalf("no cancel control on a running build:\n%s", body)
 91	}
 92	if !strings.Contains(body, "next check") {
 93		t.Fatalf("cancel control does not warn it is not instant:\n%s", body)
 94	}
 95
 96	// Cancelling the running build from the page lands where the CLI sees
 97	// it, including the runner-facing wording that it stops at its next
 98	// check rather than right away.
 99	if status, _ := browserPost(t, alice, build2+"/cancel", url.Values{}); status != 200 {
100		t.Fatalf("cancel running build: %d", status)
101	}
102	out, _, _ := inst.ssh(t, aliceKey, "", "build", "list", "alice/app")
103	if !strings.Contains(out, "unit\tcancelled") {
104		t.Fatalf("running build not cancelled: %s", out)
105	}
106	log, _, _ := inst.ssh(t, aliceKey, "", "build", "log", "alice/app", "2")
107	if !strings.Contains(log, "cancelled by alice while running") {
108		t.Fatalf("log missing the while-running cancellation: %s", log)
109	}
110	// Cancelled, so the control is gone here too.
111	if _, body = browserGet(t, alice, build2); strings.Contains(body, "/cancel") {
112		t.Fatalf("cancel control still on a cancelled build:\n%s", body)
113	}
114
115	// A stale or repeated post against a build that can no longer be
116	// cancelled is refused, and the page says so rather than breaking.
117	status, body := browserPost(t, alice, build1+"/cancel", url.Values{})
118	if status != 200 {
119		t.Fatalf("re-cancel post: %d", status)
120	}
121	if !strings.Contains(body, `class="error"`) || !strings.Contains(body, "cancelled") {
122		t.Fatalf("refusal not surfaced on the page:\n%s", body)
123	}
124}