Skip to content
quarry Artifact [e57e5832de]
Login

Artifact [e57e5832de]

Artifact e57e5832dec2faef995e571432831e944a315df170cbf4dfd5f958cb5da1070a:


package web

import (
	"net/http/httptest"
	"net/url"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/ci"
	"craftdesign.group/quarry/internal/ops"
)

func cancelServer(t *testing.T, letters string, runs []api.CIRun, cancelled *[]string) *httptest.Server {
	t.Helper()
	ix := landingIndex(t, false)
	r := ops.NewRegistry()
	deps := api.Deps{Version: "v",
		Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix}}),
		CI:    fakeCI{runs: runs},
		CICancel: func(repo, runID, by string) error {
			*cancelled = append(*cancelled, repo+"/"+runID+"/"+by)
			return nil
		}}
	if err := api.RegisterAll(r, deps); err != nil {
		t.Fatal(err)
	}
	eng := fakeEngine{run: ci.Run{RunID: "run-1", Workflow: "quarry", Status: "running"}}
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"alpha": ops.Admin}}),
		WithCaps(stubCaps{letters: letters}), WithEngine(eng)},
		Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })))
	t.Cleanup(srv.Close)
	return srv
}

// #297: a repository admin can cancel a run that is going, from its own
// page; nobody else is offered the control, and a finished run has none.
// A run the forge ended says why.
func TestRunPageOffersCancelToAdminsOfARunningRun(t *testing.T) {
	runs := []api.CIRun{
		{RunID: "run-1", Checkin: "abc", Workflow: "quarry", Pipeline: "ci/quarry.json", Status: "running", Started: "2026-08-29T01:00:00Z"},
		{RunID: "run-2", Checkin: "abc", Workflow: "quarry", Pipeline: "ci/quarry.json", Status: "completed", Started: "2026-08-29T01:00:00Z"},
		{RunID: "run-3", Checkin: "abc", Workflow: "quarry", Pipeline: "ci/quarry.json", Status: "cancelled", Started: "2026-08-29T01:00:00Z", Reason: "expired"},
	}
	var cancelled []string
	admin := cancelServer(t, "ia8", runs, &cancelled)
	if got := len(hookAttrs(asDan(t, admin, "/r/alpha/ci/run/run-1"), "run-cancel")); got != 1 {
		t.Errorf("an admin sees %d cancel controls on a running run, want 1", got)
	}
	for _, id := range []string{"run-2", "run-3"} {
		if got := len(hookAttrs(asDan(t, admin, "/r/alpha/ci/run/"+id), "run-cancel")); got != 0 {
			t.Errorf("a finished run %s offers %d cancel controls, want 0", id, got)
		}
	}
	if got := len(hookAttrs(asDan(t, admin, "/r/alpha/ci/run/run-3"), "run-reason")); got != 1 {
		t.Errorf("a run the forge ended shows %d reasons, want 1", got)
	}
	plain := cancelServer(t, "i8", runs, &cancelled)
	if got := len(hookAttrs(asDan(t, plain, "/r/alpha/ci/run/run-1"), "run-cancel")); got != 0 {
		t.Errorf("a non-admin sees %d cancel controls, want 0", got)
	}

	resp := postAsDan(t, plain, "/r/alpha/ci/run/run-1/cancel", url.Values{})
	if resp.StatusCode < 400 || len(cancelled) != 0 {
		t.Fatalf("a non-admin's cancel answered %d and reached %v, want a refusal", resp.StatusCode, cancelled)
	}
	resp = postAsDan(t, admin, "/r/alpha/ci/run/run-1/cancel", url.Values{})
	if resp.StatusCode != 303 || resp.Header.Get("Location") != "/r/alpha/ci/run/run-1" ||
		len(cancelled) != 1 || cancelled[0] != "alpha/run-1/dan" {
		t.Fatalf("admin cancel: status %d to %q, reached %v, want 303 back to the run and one cancel by dan",
			resp.StatusCode, resp.Header.Get("Location"), cancelled)
	}
}