Skip to content
quarry Artifact [e9ae8ddec0]
Login

Artifact [e9ae8ddec0]

Artifact e9ae8ddec0bad44c02893b7511b9fdbdef795480291df3e73d365e9244a180ce:


package web

import (
	"regexp"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/artifact"
	"craftdesign.group/quarry/internal/index"
	"craftdesign.group/quarry/internal/ops"
	"net/http/httptest"
	"path/filepath"
)

// Three landings chained feat-c → feat-b → feat-a → trunk, all open,
// plus one standalone. Four open landings; two decisions.
func strataIndex(t *testing.T) *index.Index {
	t.Helper()
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	root := func(hash, branch, target, title, date string) index.Parsed {
		return index.Parsed{Hash: hash, Art: &artifact.Artifact{Type: artifact.Forum,
			ThreadTitle: title, Date: date, User: "dan", Mimetype: "text/x-markdown",
			Body: []byte("zit-landing: 1\nbranch: " + branch + "\ntarget: " + target + "\n\n" + title + "\n")}}
	}
	if err := ix.FoldNow([]index.Parsed{
		// A tip on the top branch with a passing verdict: the strata's.
		{Hash: strings.Repeat("00", 20), Art: &artifact.Artifact{Type: artifact.Manifest,
			Date: "2026-08-19T09:00:00", User: "dan", Comment: "base"}},
		{Hash: strings.Repeat("cc", 20), Art: &artifact.Artifact{Type: artifact.Manifest,
			Date: "2026-08-20T09:25:00", User: "dan", Comment: "top of the stack",
			Parents: []string{strings.Repeat("00", 20)},
			Tags: []artifact.Tag{{Name: "*branch", Target: "*", Value: "feat-c"},
				{Name: "*sym-feat-c", Target: "*"}, {Name: "+ci-pass", Target: "*"}}}},
		root(strings.Repeat("a1", 20), "feat-a", "trunk", "phase one", "2026-08-20T09:00:00"),
		root(strings.Repeat("b2", 20), "feat-b", "feat-a", "phase two", "2026-08-20T09:10:00"),
		root(strings.Repeat("c3", 20), "feat-c", "feat-b", "phase three", "2026-08-20T09:20:00"),
		root(strings.Repeat("d4", 20), "lone", "trunk", "a loner", "2026-08-20T09:30:00"),
	}); err != nil {
		t.Fatal(err)
	}
	if err := ix.RecomputeBranches(); err != nil {
		t.Fatal(err)
	}
	return ix
}

func strataServer(t *testing.T, ix *index.Index) *httptest.Server {
	t.Helper()
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "v",
		Repos: api.NewRepos(map[string]*api.Repo{"alpha": {Index: ix}})}); err != nil {
		t.Fatal(err)
	}
	srv := httptest.NewServer(HandlerWith(r, nil, nil,
		[]Option{WithSessions(fakeSessions{user: "dan"}), WithCaps(stubCaps{letters: "i8"})}))
	t.Cleanup(srv.Close)
	return srv
}

// RFC 0006: a strata that looks like three landings is worse than no
// strata feature. The open queue shows it as ONE row, counts it as one
// decision, and folds the rungs under it with their positions.
func TestStrataListsAsOneDecision(t *testing.T) {
	srv := strataServer(t, strataIndex(t))
	html := asDan(t, srv, "/r/alpha/landing")
	if strings.Count(html, `<li class="t-row`) != 2 {
		t.Fatalf("want 2 rows (the stack and the loner), got %d:\n%.1200s", strings.Count(html, `<li class="t-row`), html)
	}
	if !strings.Contains(html, `t-strata`) || !strings.Contains(html, "3 segments") {
		t.Errorf("the stack row is not marked as a strata of 3")
	}
	if !strings.Contains(html, `title="2">2</span> Open<`) {
		i := strings.Index(html, "Open</a>")
		if i < 0 {
			i = len(html)
		}
		t.Errorf("the count chip does not count decisions (want 2 Open): %q", html[max(0, i-160):min(len(html), i+12)])
	}
	for _, badge := range []string{">1/3<", ">2/3<", ">3/3<"} {
		if !strings.Contains(html, badge) {
			t.Errorf("rung badge %s missing", badge)
		}
	}
	if !strings.Contains(html, "<details class=\"strata\">") {
		t.Error("the rungs are not folded under the base without script")
	}
	// The top rung passed CI, and its tip contains everything below:
	// that is the strata's verdict, on the row.
	if !strings.Contains(html, "stack <span class=\"tl-badge ci-pass\"") {
		t.Errorf("the stack's verdict is not on the row:\n%.900s", html)
	}
	// The settled views stay flat: nothing to group.
	if all := asDan(t, srv, "/r/alpha/landing?state=all"); strings.Contains(all, "t-strata") {
		t.Error("the flat view grouped")
	}
}

// The record page: a map with this rung marked and the landable one
// named; above the bottom, the accept control is disabled and says why.
func TestStrataMapOnTheRecord(t *testing.T) {
	srv := strataServer(t, strataIndex(t))
	top := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("c3", 6))
	if !strings.Contains(top, `class="strata-map"`) || !strings.Contains(top, "3 segments") {
		t.Fatalf("no stack map on the top rung:\n%.1200s", top)
	}
	if !strings.Contains(top, `aria-current="true"`) {
		t.Error("the current rung is not marked")
	}
	if !strings.Contains(top, "stack <span class=\"tl-badge ci-pass\"") {
		t.Error("the map does not carry the strata's verdict")
	}
	if !strings.Contains(top, "phase one</a>") || !strings.Contains(top, "· landable") {
		t.Error("the landable base is not named in the map")
	}
	if !strings.Contains(top, "stacked — the segment below lands first") {
		t.Error("the accept control above the bottom is not disabled with the reason")
	}
	base := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6))
	if !strings.Contains(base, `formaction="/r/alpha/landing/a1a1a1a1a1a1/accept"`) {
		t.Error("the base's accept control is missing")
	}
	// Landing the whole strata is offered at the base, aimed at the top;
	// landing up to a rung is offered on that rung.
	// The FORMACTION is the contract — it names the top rung, which is
	// what "land the whole strata" means. The rung COUNT must reach the
	// reader too, but its wording is display text and changes with the
	// viewport, so the assertion is on the number, not the sentence.
	if !strings.Contains(base, `formaction="/r/alpha/landing/c3c3c3c3c3c3/land"`) || !strings.Contains(base, "3") {
		t.Error("the base does not offer to land the strata")
	}
	if !strings.Contains(top, `formaction="/r/alpha/landing/c3c3c3c3c3c3/land"`) || !strings.Contains(top, "Land up to here (3 rungs)") {
		t.Error("the top rung does not offer to land up to itself")
	}
	// A standalone landing has no map.
	if lone := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("d4", 6)); strings.Contains(lone, `class="strata-map"`) {
		t.Error("a standalone landing drew a stack map")
	}
}

// Past twenty rungs the folded list and the map stop helping and start
// scrolling (GitLab's cap). The chain is still read and landable; the
// page says how tall it is and points at the base.
func TestTooTallStrataIsNotDrawn(t *testing.T) {
	var g landingGroup
	g.ID, g.Title, g.Branch, g.Target, g.State = strings.Repeat("a1", 20), "base", "r1", "trunk", "open"
	g.Stack, g.Position, g.Depth = g.ID, 1, index.MaxDrawnRungs+1
	for i := 1; i <= index.MaxDrawnRungs+1; i++ {
		g.Segments = append(g.Segments, landingRow{ID: strings.Repeat("b2", 20), Title: "rung", State: "open",
			Stack: g.ID, Position: i, Depth: index.MaxDrawnRungs + 1})
	}
	if !g.TooTall() {
		t.Fatal("21 rungs is not too tall")
	}
	var buf strings.Builder
	if err := noHooks.template(landingListPage).ExecuteTemplate(&buf, "content", pageData{Repo: "alpha",
		LandingGroups: []landingGroup{g}, LandingOpen: 1, Nav: ticketNav{Repo: "alpha"}}); err != nil {
		t.Fatal(err)
	}
	html := buf.String()
	if strings.Contains(html, `<details class="strata">`) {
		t.Error("a too-tall strata was folded anyway")
	}
	if !strings.Contains(html, "too tall to fold here") {
		t.Error("the page does not say the strata is too tall")
	}
	g.Segments = g.Segments[:index.MaxDrawnRungs]
	if g.TooTall() {
		t.Error("exactly the cap counts as too tall")
	}
}

// A rung is one line: a landing whose description is a paragraph shows
// its first sentence clipped, not the paragraph as a link.
func TestAStrataRungIsOneLine(t *testing.T) {
	ix, err := index.Open(filepath.Join(t.TempDir(), "index.db"))
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { ix.Close() })
	long := strings.Repeat("A landing description written as one long paragraph of prose. ", 6)
	root := func(hash, branch, target string) index.Parsed {
		return index.Parsed{Hash: hash, Art: &artifact.Artifact{Type: artifact.Forum, ThreadTitle: long, Date: "2026-08-20T09:00:00",
			User: "dan", Mimetype: "text/x-markdown", Body: []byte("zit-landing: 1\nbranch: " + branch + "\ntarget: " + target + "\n\n" + long + "\n")}}
	}
	if err := ix.FoldNow([]index.Parsed{root(strings.Repeat("a1", 20), "one", "trunk"), root(strings.Repeat("b2", 20), "two", "one")}); err != nil {
		t.Fatal(err)
	}
	if err := ix.RecomputeBranches(); err != nil {
		t.Fatal(err)
	}
	html := asDan(t, strataServer(t, ix), "/r/alpha/landing")
	links := regexp.MustCompile(`class="rung-t"[^>]*>([^<]*)</a>`).FindAllStringSubmatch(html, -1)
	if len(links) != 2 {
		t.Fatalf("FAIL: %d rung links, want 2", len(links))
	}
	for _, l := range links {
		if n := len([]rune(l[1])); n > 73 {
			t.Errorf("FAIL: a rung prints %d characters of its description", n)
		}
	}
	t.Log("ok: both rungs clipped to a line")
}