Skip to content
quarry Artifact [587b64c8e6]
Login

Artifact [587b64c8e6]

Artifact 587b64c8e6f81bbdf19dc7f1807d8f02b527cba4652ed3087bcb1d653e8d3e94:


package web

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"
	"regexp"
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/oltp"

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

type claimLookIn struct {
	Code string `json:"code"`
}
type claimIn struct {
	Code  string `json:"code"`
	Repos string `json:"repos"`
	Role  string `json:"role"`
}
type claimOut struct {
	Request string `json:"request"`
}

var nonceRe = regexp.MustCompile(`nonce="[^"]*"|<span class="f-time".*</span></span>`)

func repoRows(names ...string) []setupRepo {
	var out []setupRepo
	for _, n := range names {
		out = append(out, setupRepo{Name: n, Handle: n, Visibility: "private", Namespace: "ann"})
	}
	return out
}

type claimNone struct{}
type claimVersion struct {
	Version string `json:"version"`
}

// claimForge is the /setup pages over stand-ins for the three ops, so
// what is tested is the pages' own reading of them.
type claimForge struct {
	h      http.Handler
	signed bool
	kind   string
	repos  []setupRepo
	// addr is the address the stub says the start came from; limited makes
	// the look op answer rate-limited.
	addr    string
	limited bool
	// problem is said on the found code; refuse is the sentence a claim
	// is refused with.
	problem, refuse string
	claimed         []claimIn
	discarded       int
}

func newClaimForge(t *testing.T) *claimForge {
	t.Helper()
	f := &claimForge{signed: true, kind: "read", repos: repoRows("ctx", "suit"), addr: "198.51.100.4"}
	r := ops.NewRegistry()
	// The page chrome asks the forge which build it is.
	if err := ops.Register(r, ops.Op[claimNone, claimVersion]{Name: "meta.version", Doc: "d", Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/version",
		Run: func(context.Context, claimNone) (claimVersion, error) { return claimVersion{Version: "t"}, nil }}); err != nil {
		t.Fatal(err)
	}
	notFound := func() error { return fmt.Errorf("no such code: %w", ops.ErrNotFound) }
	if err := ops.Register(r, ops.Op[claimLookIn, setupLook]{Name: "setup.start.look", Doc: "d", Kind: ops.Command, Auth: ops.Token, Route: "POST /api/v1/setup/look",
		Run: func(_ context.Context, in claimLookIn) (setupLook, error) {
			if f.limited {
				return setupLook{}, fmt.Errorf("slow down: %w", ops.ErrRateLimited)
			}
			if strings.ToUpper(in.Code) != "ABCD-EFGH" {
				return setupLook{}, notFound()
			}
			l := setupLook{Code: "ABCD-EFGH", Name: "e-desktop", Kind: f.kind, Address: f.addr, Created: time.Now().UTC().Format(time.RFC3339),
				Repos: f.repos, Roles: []string{}, Problem: f.problem}
			if f.kind == "push" {
				l.Roles = []string{"developer", "publisher", "maintainer"}
			}
			return l, nil
		}}); err != nil {
		t.Fatal(err)
	}
	if err := ops.Register(r, ops.Op[claimIn, claimOut]{Name: "setup.start.claim", Doc: "d", Kind: ops.Command, Auth: ops.Token, Route: "POST /api/v1/setup/claim",
		Run: func(_ context.Context, in claimIn) (claimOut, error) {
			if strings.ToUpper(in.Code) != "ABCD-EFGH" {
				return claimOut{}, notFound()
			}
			if f.refuse != "" {
				return claimOut{}, fmt.Errorf("%s: %w", f.refuse, ops.ErrInvalid)
			}
			f.claimed = append(f.claimed, in)
			return claimOut{Request: "req123"}, nil
		}}); err != nil {
		t.Fatal(err)
	}
	if err := ops.Register(r, ops.Op[claimLookIn, claimNone]{Name: "setup.start.discard", Doc: "d", Kind: ops.Command, Auth: ops.Token, Route: "POST /api/v1/setup/discard",
		Run: func(_ context.Context, in claimLookIn) (claimNone, error) {
			if strings.ToUpper(in.Code) != "ABCD-EFGH" {
				return claimNone{}, notFound()
			}
			f.discarded++
			return claimNone{}, nil
		}}); err != nil {
		t.Fatal(err)
	}
	f.h = HandlerWith(r, nil, nil, []Option{WithSessions(fakeSessions{user: "jva"})})
	return f
}

func (f *claimForge) do(method, path string, form url.Values, signed bool) *httptest.ResponseRecorder {
	var req *http.Request
	if method == "POST" {
		req = httptest.NewRequest("POST", path, strings.NewReader(form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	} else {
		req = httptest.NewRequest("GET", path, nil)
	}
	req.RemoteAddr = "198.51.100.4:1234"
	if signed {
		req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	}
	rec := httptest.NewRecorder()
	f.h.ServeHTTP(rec, req)
	return rec
}

func TestTheCodePagesAreForAPersonSignedIn(t *testing.T) {
	f := newClaimForge(t)
	for _, c := range []struct{ method, path string }{{"GET", "/setup"}, {"POST", "/setup"}, {"POST", "/setup/claim"}} {
		rec := f.do(c.method, c.path, url.Values{"code": {"ABCD-EFGH"}, "repos": {"ctx"}}, false)
		if rec.Code != http.StatusSeeOther || !strings.HasPrefix(rec.Header().Get("Location"), "/login") {
			t.Errorf("FAIL: %s %s without a session answered %d to %q", c.method, c.path, rec.Code, rec.Header().Get("Location"))
		}
	}
	if len(f.claimed) != 0 {
		t.Errorf("FAIL: a claim ran without a session")
	}
	rec := f.do("GET", "/setup", nil, true)
	body := rec.Body.String()
	if rec.Code != 200 || !hasHook(body, "setup-entry") || !hasHook(body, "setup-code") || !hasHook(body, "setup-look") || hasHook(body, "setup-nocode") {
		t.Errorf("FAIL: the code page: %d", rec.Code)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestEveryCodeNotHeldIsOnePageState(t *testing.T) {
	f := newClaimForge(t)
	var first string
	for i, code := range []string{"ZZZZ-ZZZZ", "not a code", "", strings.Repeat("A", 200), "abcd efgh!"} {
		rec := f.do("POST", "/setup", url.Values{"code": {code}}, true)
		body := nonceRe.ReplaceAllString(rec.Body.String(), "")
		if rec.Code != http.StatusNotFound || !hasHook(body, "setup-nocode") || hasHook(body, "setup-claim") {
			t.Errorf("FAIL: code %q answered %d", code, rec.Code)
		}
		if i == 0 {
			first = body
		} else if body != first {
			t.Errorf("FAIL: code %q draws a different page", code)
		}
	}
	// A claim and a discard of a code not held read the same.
	for _, form := range []url.Values{{"code": {"ZZZZ-ZZZZ"}, "do": {"continue"}, "repos": {"ctx"}}, {"code": {"ZZZZ-ZZZZ"}, "do": {"discard"}}} {
		rec := f.do("POST", "/setup/claim", form, true)
		if rec.Code != http.StatusNotFound && rec.Code != http.StatusTooManyRequests || strings.Contains(rec.Body.String(), "req123") {
			t.Errorf("FAIL: %v answered %d", form, rec.Code)
		}
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestARateLimitedLookIsItsOwnState(t *testing.T) {
	f := newClaimForge(t)
	f.limited = true
	rec := f.do("POST", "/setup", url.Values{"code": {"ABCD-EFGH"}}, true)
	if rec.Code != http.StatusTooManyRequests || rec.Header().Get("Retry-After") == "" || !hasHook(rec.Body.String(), "setup-held") {
		t.Errorf("FAIL: a limited look answered %d (Retry-After %q)", rec.Code, rec.Header().Get("Retry-After"))
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestTheClaimPageShowsWhatWasAskedForAndOffersOnlyTheListedRepositories(t *testing.T) {
	f := newClaimForge(t)
	rec := f.do("POST", "/setup", url.Values{"code": {"ABCD-EFGH"}}, true)
	body := rec.Body.String()
	if rec.Code != 200 {
		t.Fatalf("FAIL: %d", rec.Code)
	}
	for _, hook := range []string{"setup-claim", "setup-claim-name", "setup-claim-kind", "setup-claim-code", "setup-claim-from", "setup-claim-when", "setup-claim-warn", "setup-claim-discard", "setup-claim-continue"} {
		if !hasHook(body, hook) {
			t.Errorf("FAIL: the page has no %s", hook)
		}
	}
	if hookCount(body, "setup-claim-repo") != 2 || strings.Count(body, `type="checkbox" name="repos"`) != 2 || strings.Contains(body, `type="radio"`) || hasHook(body, "setup-claim-role") {
		t.Errorf("FAIL: a read client's picker: %d repositories", hookCount(body, "setup-claim-repo"))
	}
	if v, _ := hookAttr(t, body, "setup-claim", "data-kind"); v != "read" {
		t.Errorf("FAIL: data-kind %q", v)
	}
	if v, _ := hookAttr(t, body, "setup-claim-from", "data-address"); v != "198.51.100.4" {
		t.Errorf("FAIL: data-address %q", v)
	}
	// The start came from the network the person is on: no warning.
	if v, _ := hookAttr(t, body, "setup-claim-from", "data-match"); v != "true" || hasHook(body, "setup-claim-elsewhere") {
		t.Errorf("FAIL: a start from this network: match %q, warning %v", v, hasHook(body, "setup-claim-elsewhere"))
	}
	// A start from another network is said, as data, not refused.
	f.addr = "203.0.113.5"
	body = f.do("POST", "/setup", url.Values{"code": {"ABCD-EFGH"}}, true).Body.String()
	if v, _ := hookAttr(t, body, "setup-claim-from", "data-match"); v != "false" || !hasHook(body, "setup-claim-elsewhere") || !hasHook(body, "setup-claim-continue") {
		t.Errorf("FAIL: a start from elsewhere: match %q", v)
	}
	if v, _ := hookAttr(t, body, "setup-claim-elsewhere", "data-match"); v != "false" {
		t.Errorf("FAIL: the warning's data-match %q", v)
	}
	// Two addresses of one IPv6 /64 are one network.
	f.addr = oltp.AddrGroup("2001:db8:5:6::1")
	req := httptest.NewRequest("POST", "/setup", strings.NewReader(url.Values{"code": {"ABCD-EFGH"}}.Encode()))
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.RemoteAddr = "[2001:db8:5:6:aaaa::7]:1234"
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	rec6 := httptest.NewRecorder()
	f.h.ServeHTTP(rec6, req)
	if v, _ := hookAttr(t, rec6.Body.String(), "setup-claim-from", "data-match"); v != "true" {
		t.Errorf("FAIL: an address in the same /64 did not match: %q", v)
	}
	f.addr = "198.51.100.4"
	if strings.Contains(rec.Header().Get("Location"), "ABCD") {
		t.Errorf("FAIL: the code is in a header")
	}

	f.kind, f.repos = "push", repoRows("suit", "ctx", "wiki")
	body = f.do("POST", "/setup", url.Values{"code": {"abcd-efgh"}}, true).Body.String()
	if body == "" || hookCount(body, "setup-claim") == 0 {
		t.Fatalf("FAIL: a code typed in lower case is not read")
	}
	if strings.Count(body, `type="radio" name="repos"`) != 3 || strings.Contains(body, `type="checkbox"`) || !hasHook(body, "setup-claim-role") ||
		strings.Count(body, "<option") != 3 || !strings.Contains(body, `<option value="developer" selected>`) {
		t.Errorf("FAIL: a pushing client's picker: radios %d, options %d", strings.Count(body, `type="radio"`), strings.Count(body, "<option"))
	}

	f.repos, f.problem = nil, "There is nothing."
	body = f.do("POST", "/setup", url.Values{"code": {"ABCD-EFGH"}}, true).Body.String()
	if hookCount(body, "setup-claim-repo") != 0 || hasHook(body, "setup-claim-continue") || !hasHook(body, "setup-claim-empty") || !hasHook(body, "setup-claim-discard") || hasHook(body, "setup-claim-repos") {
		t.Errorf("FAIL: a code with nothing to offer still offers")
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestContinueFilesAndLandsOnTheRequestAndDiscardThrowsTheStartAway(t *testing.T) {
	f := newClaimForge(t)
	rec := f.do("POST", "/setup/claim", url.Values{"code": {"ABCD-EFGH"}, "do": {"continue"}, "repos": {"ctx", "suit"}}, true)
	if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/me/requests/req123" || len(f.claimed) != 1 ||
		f.claimed[0].Repos != "ctx,suit" || strings.Contains(rec.Header().Get("Location"), "ABCD") {
		t.Errorf("FAIL: continue answered %d to %q with %+v", rec.Code, rec.Header().Get("Location"), f.claimed)
	}
	rec = f.do("POST", "/setup/claim", url.Values{"code": {"ABCD-EFGH"}, "do": {"discard"}}, true)
	if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/setup" || f.discarded != 1 {
		t.Errorf("FAIL: discard answered %d to %q (%d discarded)", rec.Code, rec.Header().Get("Location"), f.discarded)
	}
	// Refused for what was picked: the page again, the picks kept and the
	// sentence in its own element.
	f.refuse = "ctx is not yours"
	rec = f.do("POST", "/setup/claim", url.Values{"code": {"ABCD-EFGH"}, "do": {"continue"}, "repos": {"suit"}}, true)
	body := rec.Body.String()
	if rec.Code != http.StatusBadRequest || !hasHook(body, "setup-claim-error") || !strings.Contains(body, `value="suit" data-hook="setup-claim-repo" checked`) || strings.Contains(body, `value="ctx" data-hook="setup-claim-repo" checked`) {
		t.Errorf("FAIL: a refused claim: %d", rec.Code)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestAPickerOfRowsFiltersWhenLong(t *testing.T) {
	f := newClaimForge(t)
	body := f.do("POST", "/setup", url.Values{"code": {"ABCD-EFGH"}}, true).Body.String()
	if hasHook(body, "setup-claim-filter") || hookCount(body, "setup-claim-row") != 2 || !hasHook(body, "setup-claim-count") {
		t.Errorf("FAIL: a short list: rows %d, filter %v", hookCount(body, "setup-claim-row"), hasHook(body, "setup-claim-filter"))
	}
	var names []string
	for i := 0; i < 15; i++ {
		names = append(names, fmt.Sprintf("repo-%02d", i))
	}
	f.repos = repoRows(names...)
	body = f.do("POST", "/setup", url.Values{"code": {"ABCD-EFGH"}}, true).Body.String()
	if !hasHook(body, "setup-claim-filter") || hookCount(body, "setup-claim-row") != 15 || strings.Count(body, `<label class="claim-row">`) != 15 {
		t.Errorf("FAIL: a long list: rows %d, filter %v", hookCount(body, "setup-claim-row"), hasHook(body, "setup-claim-filter"))
	}
	if !t.Failed() {
		t.Log("ok")
	}
}

func TestAClientStartedRequestNamesNoAccount(t *testing.T) {
	render := func(agent string) string {
		var b strings.Builder
		s := meSignOff{ID: "abc", Agent: agent, Summary: "Set up agent e to read x", State: "pending", Requested: "2026-11-13T10:00:00Z"}
		if err := noHooks.template(signOffPageTmpl).ExecuteTemplate(&b, "content", pageData{Title: "Approve?", SignOff: &s}); err != nil {
			t.Fatal(err)
		}
		return b.String()
	}
	client := render("client of ann")
	if v, _ := hookAttr(t, client, "signoff-agent", "data-client"); v != "true" || strings.Contains(client, "client of ann") {
		t.Errorf("FAIL: a client's request: data-client %q, label shown %v", v, strings.Contains(client, "client of ann"))
	}
	if v, ok := hookAttr(t, render("zeitgeist"), "signoff-agent", "data-client"); ok && v != "" {
		t.Errorf("FAIL: an agent's request is marked client")
	}
	var b strings.Builder
	if err := noHooks.template(requestsPageTmpl).ExecuteTemplate(&b, "content", pageData{Title: "Requests", Requests: &requestsView{Tab: "waiting",
		Waiting: []meSignOff{{ID: "abc", Agent: "client of ann", Summary: "x", State: "pending", Requested: "2026-11-13T10:00:00Z"}}}}); err != nil {
		t.Fatal(err)
	}
	if v, _ := hookAttr(t, b.String(), "request-asker", "data-client"); v != "true" || strings.Contains(b.String(), "client of ann") {
		t.Errorf("FAIL: the list row: data-client %q", v)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}