Skip to content
quarry Artifact [6f7e75a43f]
Login

Artifact [6f7e75a43f]

Artifact 6f7e75a43f2398ba545ae9c411843e6e4475e37d3b80d74f7e1cdad3ee9b35d4:


package web

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

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

// twoTools is a forge after RFC 0025: two repositories called tools, one
// under dan and one under a team, with keys that are not their handles,
// beside quarry, whose key is its handle as every older repository's is.
func twoTools() *Addresses {
	where := map[string][2]string{
		"r1":     {"dan", "tools"},
		"r2":     {"crew", "tools"},
		"quarry": {"dmestas", "quarry"},
	}
	return &Addresses{
		Of: func(key string) (string, string, bool) {
			a, ok := where[key]
			return a[0], a[1], ok
		},
		At: func(ns, handle string) (string, bool) {
			for key, a := range where {
				if a[0] == ns && a[1] == handle {
					return key, true
				}
			}
			return "", false
		},
		Named: func(handle string) []string {
			var out []string
			for _, key := range []string{"quarry", "r1", "r2"} {
				if where[key][1] == handle {
					out = append(out, key)
				}
			}
			return out
		},
	}
}

func serveAddr(addr *Addresses, see func(string, *http.Request) bool, method, path string) (code int, served, location, body string) {
	inner := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { served = req.URL.Path })
	w := httptest.NewRecorder()
	namespaced(addr, nil, see, inner).ServeHTTP(w, httptest.NewRequest(method, path, nil))
	return w.Code, served, w.Header().Get("Location"), w.Body.String()
}

// An address reaches its repository's key, on pages, sync and the API;
// a key is sent to its address; and a handle two repositories share is
// never guessed.
func TestAnAddressReachesItsRepository(t *testing.T) {
	a := twoTools()
	for _, c := range []struct {
		method, path  string
		served, moved string
		code          int
		bodyHas       string
	}{
		{"GET", "/r/dan/tools/timeline", "/r/r1/timeline", "", 200, ""},
		{"POST", "/r/crew/tools/xfer", "/r/r2/xfer", "", 200, ""},
		{"GET", "/api/v1/r/dan/tools/ticket", "/api/v1/r/r1/ticket", "", 200, ""},
		{"GET", "/r/r1/wiki", "", "/r/dan/tools/wiki", 302, ""},
		{"GET", "/r/quarry/timeline", "", "/r/dmestas/quarry/timeline", 302, ""},
		{"GET", "/r/tools", "/named/tools", "", 200, ""},
		{"POST", "/r/tools/xfer", "", "", 409, "/r/dan/tools"},
		// The API has no reader to filter by, so a shared handle answers
		// as a name nobody has rather than listing private repositories.
		{"GET", "/api/v1/r/tools/ticket", "/api/v1/r/tools/ticket", "", 200, ""},
		{"GET", "/r/nobody/tools", "/r/nobody/tools", "", 200, ""},
	} {
		code, served, loc, body := serveAddr(a, allVisible, c.method, c.path)
		if code != c.code || served != c.served || loc != c.moved || !strings.Contains(body, c.bodyHas) {
			t.Errorf("FAIL: %s %s = %d served %q moved %q body %q; want %d %q %q",
				c.method, c.path, code, served, loc, strings.TrimSpace(body), c.code, c.served, c.moved)
		}
	}
	if got := a.url("r2"); got != "/r/crew/tools" {
		t.Errorf("FAIL: r2's link is %q", got)
	}
	if got := repoOfPath("/r/dan/tools/forum", a); got != "r1" {
		t.Errorf("FAIL: the path's repository is %q, want r1", got)
	}
}

// A handle the reader can see on only one repository is that one: the
// other is private to them, so it is not there to be confused with.
func TestAHandleOnlyOneVisibleRepositoryHasIsThatOne(t *testing.T) {
	onlyR1 := func(key string, _ *http.Request) bool { return key != "r2" }
	if code, _, loc, _ := serveAddr(twoTools(), onlyR1, "GET", "/r/tools/timeline"); code != 302 || loc != "/r/dan/tools/timeline" {
		t.Errorf("FAIL: GET /r/tools = %d to %q, want the one the reader can see", code, loc)
	}
	if _, served, _, _ := serveAddr(twoTools(), onlyR1, "POST", "/r/tools/xfer"); served != "/r/r1/xfer" {
		t.Errorf("FAIL: sync to /r/tools served %q, want r1", served)
	}
	if _, served, loc, _ := serveAddr(twoTools(), onlyR1, "GET", "/r/crew/tools"); served != "/r/crew/tools" || loc != "" {
		t.Errorf("FAIL: a repository the reader cannot see was reached at its address (%q, %q)", served, loc)
	}
}

// A bare handle two repositories share is a page listing both, by
// address, through the whole handler.
func TestABareSharedHandleListsItsRepositories(t *testing.T) {
	r := ops.NewRegistry()
	if err := ops.Register(r, ops.Op[struct{}, struct {
		Version string `json:"version"`
	}]{
		Name: "meta.version", Doc: "test double", Kind: ops.Query, Auth: ops.Anonymous, Route: "GET /api/v1/version",
		Run: func(context.Context, struct{}) (struct {
			Version string `json:"version"`
		}, error) {
			return struct {
				Version string `json:"version"`
			}{"t"}, nil
		},
	}); err != nil {
		t.Fatal(err)
	}
	h := HandlerWith(r, nil, nil, []Option{WithAddresses(*twoTools())})
	w := httptest.NewRecorder()
	h.ServeHTTP(w, httptest.NewRequest("GET", "/r/tools", nil))
	body := w.Body.String()
	if w.Code != 200 || !strings.Contains(body, `data-hook="named"`) ||
		!strings.Contains(body, `href="/r/dan/tools"`) || !strings.Contains(body, `href="/r/crew/tools"`) {
		t.Errorf("FAIL: /r/tools = %d, without both repositories listed:\n%s", w.Code, body)
	}
}

// A repository under no namespace whose handle is not its key is at
// /r/<handle>: its key is sent there, and there it is served, never sent
// to itself again.
func TestAnAddressThatIsAlreadyHereIsServed(t *testing.T) {
	a := &Addresses{
		Of: func(key string) (string, string, bool) { return "", "h", key == "k" },
		At: func(string, string) (string, bool) { return "", false },
		Named: func(handle string) []string {
			if handle == "h" {
				return []string{"k"}
			}
			return nil
		},
	}
	if code, _, loc, _ := serveAddr(a, allVisible, "GET", "/r/k/timeline"); code != 302 || loc != "/r/h/timeline" {
		t.Errorf("FAIL: the key went %d to %q, want its address", code, loc)
	}
	if code, served, loc, _ := serveAddr(a, allVisible, "GET", "/r/h/timeline"); code != 200 || served != "/r/k/timeline" || loc != "" {
		t.Errorf("FAIL: its address answered %d served %q moved %q, want it served", code, served, loc)
	}
}

// A page says a repository's name, not its key (RFC 0025): r1 is dan's
// tools, and its bar and links say so. A key is minted only when the
// name could not be one, so this is the case the reader would otherwise
// meet as rp-letters.
func TestAPageSaysTheRepositorysName(t *testing.T) {
	h := &handlerHooks{addr: twoTools()}
	h.drawPages()
	var buf strings.Builder
	if err := h.template(aboutPage).Execute(&buf, pageData{Repo: "r1", Title: "about"}); err != nil {
		t.Fatal(err)
	}
	page := buf.String()
	if !strings.Contains(page, `href="/r/dan/tools"`) || !strings.Contains(page, `</span>tools</a>`) {
		t.Errorf("FAIL: the bar does not name dan/tools")
	}
	if strings.Contains(page, `>r1<`) || strings.Contains(page, `</span>r1</a>`) {
		t.Errorf("FAIL: the page says the key r1 where a reader reads the name")
	}
	if got := h.repoName("quarry"); got != "quarry" {
		t.Errorf("FAIL: a repository whose key is its name is called %q", got)
	}
}