e2e/buildcancelweb_test.go

e6cd75b5f28bacf51620bb531320c30fd4e66bfd
gitbay/e2e/buildcancelweb_test.go history · blame · raw

123 lines · 5349 bytes

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