Skip to content
quarry Artifact [a6c0b4a05e]
Login

Artifact [a6c0b4a05e]

Artifact a6c0b4a05e18307f3b9349221673a800628abab633f23757327429bbfbef14ed:


package web

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

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

type stubCaps struct{ letters string }

func (s stubCaps) CapsOf(string, string) CapSet { return s }
func (s stubCaps) HasAny(letters string) bool   { return strings.ContainsAny(letters, s.letters) }

// stubPolicy protects trunk for alice; dan holds `i` and owns nothing.
type stubPolicy struct{ owner string }

func (p stubPolicy) MayJoin(_, user, branch string) bool { return branch != "trunk" || user == p.owner }
func (p stubPolicy) Protections(string) ([]oltp.Protection, error) {
	return []oltp.Protection{{Branch: "trunk", Owners: []string{p.owner}}}, nil
}

func protectedLandingServer(t *testing.T, ix *index.Index, owner string) *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}}),
		Protect: stubPolicy{owner: owner}}); 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
}

func asDan(t *testing.T, srv *httptest.Server, path string) string {
	t.Helper()
	req, _ := http.NewRequest("GET", srv.URL+path, nil)
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	resp, err := srv.Client().Do(req)
	if err != nil {
		t.Fatal(err)
	}
	defer resp.Body.Close()
	b, _ := io.ReadAll(resp.Body)
	return string(b)
}

// RFC 0007 §Testing, "non-owner with i / forge accept / control
// disabled". A control that would fail is not offered
// (docs/landings.md); one that names why is better than none — and it
// says forge policy, never a capability.
func TestProtectedTargetDisablesAcceptWithTheReason(t *testing.T) {
	srv := protectedLandingServer(t, landingIndex(t, false), "alice")
	html := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6))
	if !strings.Contains(html, `disabled title="trunk is protected; owners: alice"`) {
		t.Fatalf("no disabled control with the reason:\n%.800s", html)
	}
	if strings.Contains(html, `formaction="/r/alpha/landing/a1a1a1a1a1a1/accept"`) {
		t.Error("an enabled accept was drawn for a non-owner")
	}
	if !strings.Contains(html, "is protected on this forge") {
		t.Error("the page does not say this is the forge's policy")
	}
}

// ...and for the owner, protection is not the reason for anything.
// Whether the control is offered is mergeability's call (unknowable
// without a store behind the index, and unknowable must not read as
// clean); what this proves is that an owner is never shown the
// non-owner's refusal.
func TestOwnerIsNotRefused(t *testing.T) {
	srv := protectedLandingServer(t, landingIndex(t, false), "dan")
	html := asDan(t, srv, "/r/alpha/landing/"+strings.Repeat("a1", 6))
	if strings.Contains(html, "is protected on this forge") ||
		strings.Contains(html, `title="trunk is protected`) {
		t.Fatalf("the owner is shown the non-owner's reason:\n%.800s", html)
	}
}

// A conflicting rung gets the steps, not a sentence: the reader here has
// a conflict and a clone. And it says the rungs above follow on their
// own — merge-based rungs propagate a fix upward by the DAG, which is
// what other tools need a restack for.
func TestConflictingRungShowsTheStepsAndThatRungsAboveFollow(t *testing.T) {
	for _, want := range []string{"zit merge {{.Target}}", "zit push", "Rungs stacked on"} {
		if !strings.Contains(landingSource, want) {
			t.Errorf("landing page source lacks %q", want)
		}
	}
}