Skip to content
quarry Artifact [27f9968bf1]
Login

Artifact [27f9968bf1]

Artifact 27f9968bf19b0b41851d3beb98b34791852176ac6446549b0029769042575263:


package web

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

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

// A private repository does not exist for anyone who may not read it:
// every route under it answers exactly what an unknown repository
// answers. The redirects and the input checks used to run before the
// access check, so a signed-out visitor could tell the two apart by a
// 303, a 307 or a 400 (295).
func TestPrivateRepositoryAnswersLikeANonexistentOne(t *testing.T) {
	r := ops.NewRegistry()
	if err := api.RegisterAll(r, api.Deps{Version: "v",
		Repos: api.NewRepos(map[string]*api.Repo{"orchard": {Index: landingIndex(t, false)}, "pear": {Index: landingIndex(t, false)}}),
		// As production's repoFor: a private repository is readable
		// only by a signed-in viewer.
		CanRead: func(ctx context.Context, repo string) bool { return ops.User(ctx) != "" }}); err != nil {
		t.Fatal(err)
	}
	plane := &memCaps{stored: map[string]string{"dan": "a"}}
	srv := httptest.NewServer(HandlerWith(r, nil, nil, []Option{
		WithSessions(fakeSessions{user: "dan", roles: map[string]ops.Auth{"orchard": ops.Admin}}),
		WithCaps(plane),
		WithNamespaces(place(map[string]string{"orchard": "grove", "pear": ""})),
		WithPrivateRepos(func(repo string) bool { return repo == "orchard" || repo == "pear" }),
	}))
	t.Cleanup(srv.Close)

	get := func(path string, signedIn bool) (int, string, string, string) {
		t.Helper()
		req, _ := http.NewRequest("GET", srv.URL+path, nil)
		if signedIn {
			req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
		}
		client := *srv.Client()
		client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
		resp, err := client.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		defer resp.Body.Close()
		b, _ := io.ReadAll(resp.Body)
		return resp.StatusCode, resp.Header.Get("Location"), resp.Header.Get("Content-Type"), string(b)
	}

	// The footer reports how long this render took, which differs per
	// request and says nothing about the repository.
	timing := regexp.MustCompile(`<span class="f-time" data-volatile>.*</span></span></span>`)

	tails := []string{
		"/doc/trunk/README.md", "/history/trunk/README.md/", "/tree/trunk/src/",
		"/checkin/abc", "/checkin/../x", "/info/abc", "/attachment/abc/file.txt",
		"", "/timeline", "/wiki", "/file/trunk/README.md", "/raw/trunk/README.md",
		"/archive/trunk.tar.gz", "/zip/trunk.zip",
	}
	for _, tail := range tails {
		for _, shape := range []struct{ name, repo, private, missing string }{
			{"owner-qualified", "orchard", "/r/grove/orchard", "/r/grove/ghost"},
			{"wrong owner", "orchard", "/r/wrong/orchard", "/r/wrong/ghost"},
			{"owner-less namespaced", "orchard", "/r/orchard", "/r/ghost"},
			{"owner-less flat", "pear", "/r/pear", "/r/ghost"},
		} {
			priv := shape.private + tail
			code, loc, ctype, body := get(priv, false)
			wcode, wloc, wctype, wbody := get(shape.missing+tail, false)
			if code != wcode || loc != strings.ReplaceAll(wloc, "ghost", shape.repo) || ctype != wctype {
				t.Errorf("FAIL %s %s: private %d loc=%q %s, nonexistent %d loc=%q %s",
					shape.name, tail, code, loc, ctype, wcode, wloc, wctype)
			}
			if timing.ReplaceAllString(strings.ReplaceAll(wbody, "ghost", shape.repo), "") != timing.ReplaceAllString(body, "") {
				t.Errorf("FAIL %s %s: the body differs from an unknown repository's beyond the name", shape.name, tail)
			}
		}
	}

	// A member still gets the normal answers.
	if code, loc, _, _ := get("/r/orchard", true); code != http.StatusFound || loc != "/r/grove/orchard" {
		t.Errorf("FAIL member flat root: %d -> %q", code, loc)
	}
	// The doc route reaches its handler (this fixture has no store, so
	// it fails there rather than rendering); what matters is that it is
	// not the stranger's 404.
	if code, _, _, _ := get("/r/grove/orchard/doc/trunk/README.md", true); code == http.StatusNotFound {
		t.Errorf("FAIL member doc route answered the stranger's 404")
	}
	if code, _, _, _ := get("/r/grove/orchard/timeline", true); code != http.StatusOK {
		t.Errorf("FAIL member timeline: %d", code)
	}
}