Skip to content
quarry Artifact [fae98d7db5]
Login

Artifact [fae98d7db5]

Artifact fae98d7db5f635def3a9069c1b3ed0638afb635fe5952b96e2233467da81ea64:


package web

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

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/oltp"
	"craftdesign.group/quarry/internal/ops"
)

// fakeChecks answers RFC 0009's policy and rules from maps.
type fakeChecks struct{ required map[string][]string }

func (f fakeChecks) RequiredChecks(_, branch string) []string { return f.required[branch] }

// fakeCI serves runs for whatever check-in is asked.
type fakeCI struct{ runs []api.CIRun }

func (f fakeCI) CIRuns(string, int) ([]api.CIRun, error)       { return f.runs, nil }
func (f fakeCI) CIRunsFor(string, string) ([]api.CIRun, error) { return f.runs, nil }

func checksServer(t *testing.T, ix *index.Index, required map[string][]string, runs []api.CIRun, owner string) *httptest.Server {
	t.Helper()
	r := ops.NewRegistry()
	deps := api.Deps{Version: "v",
		Repos:  api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix}}),
		Checks: fakeChecks{required: required},
		CI:     fakeCI{runs: runs}}
	if owner != "" {
		deps.Protect = stubPolicy{owner: owner}
	}
	if err := api.RegisterAll(r, deps); err != nil {
		t.Fatal(err)
	}
	srv := httptest.NewServer(HandlerWith(r, nil, nil,
		[]Option{WithSessions(fakeSessions{user: "dan"}), WithCaps(stubCaps{letters: "i"})}))
	t.Cleanup(srv.Close)
	return srv
}

// RFC 0009 §verdicts: the panel shows a row per run, required checks
// are chipped, and a required check with no run reads waiting.
func TestChecksPanelShowsRunsAndWaiting(t *testing.T) {
	ix := landingIndex(t, false)
	srv := checksServer(t, ix,
		map[string][]string{"trunk": {"test", "sign"}},
		[]api.CIRun{{RunID: "run-1", Workflow: "test", Pipeline: "ci/test.json", Status: "success", Started: "2026-08-28"}},
		"")
	body := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6))
	for _, want := range []string{`id="l-checks"`, "ci/test.json", ">pass<", ">waiting<", "<code>sign</code>", `class="chip">required`} {
		if !strings.Contains(body, want) {
			t.Fatalf("panel lacks %q", want)
		}
	}
}

// A red required set closes the accept with the names in words; the
// override control appears only for who may use it.
func TestUnmetChecksCloseTheAccept(t *testing.T) {
	ix := landingIndex(t, false)
	srv := checksServer(t, ix,
		map[string][]string{"trunk": {"test"}},
		[]api.CIRun{{RunID: "run-1", Workflow: "test", Status: "failed"}},
		"")
	body := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6))
	if !strings.Contains(body, "required checks not green") || !strings.Contains(body, "<code>test</code>") {
		t.Fatal("the closed accept does not name the red check")
	}
	if strings.Contains(body, "Accept anyway") {
		t.Fatal("override offered to someone who may not use it")
	}
	// The op refuses too — the button is honesty, not the gate.
	resp := postAsDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6)+"/accept", url.Values{})
	if resp.StatusCode == http.StatusSeeOther {
		loc := resp.Header.Get("Location")
		if !strings.Contains(loc, "landing") {
			t.Fatalf("accept redirected oddly: %s", loc)
		}
	}
}

// A branch owner gets the override control, and the accept carries the
// stated reason.
func TestOwnerMayOverrideWithAReason(t *testing.T) {
	ix := landingIndex(t, false)
	srv := checksServer(t, ix,
		map[string][]string{"trunk": {"test"}},
		nil, "dan")
	body := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6))
	if !strings.Contains(body, "Accept anyway") || !strings.Contains(body, `name="override"`) {
		t.Fatal("owner does not get the override control")
	}
}

var _ = oltp.CheckRule{} // settings-side types live in oltp