Skip to content
quarry Artifact [552516c2de]
Login

Artifact [552516c2de]

Artifact 552516c2de1ccab0323b2f058a9d49ef7005e782e9b2f758f510d12c77f6f56f:


package web

import (
	"bytes"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

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

// fakeSessions resolves one cookie to one user and answers Allow from a
// role table keyed by repo. The empty repo is the site-admin question.
type fakeSessions struct {
	user  string
	roles map[string]ops.Auth
}

func (f fakeSessions) Login(string, string) (string, time.Time, bool) { return "", time.Time{}, false }
func (f fakeSessions) Resolve(tok string) (string, bool)              { return f.user, tok == "good" }
func (f fakeSessions) Logout(string)                                  {}
func (f fakeSessions) Allow(user, repo string, need ops.Auth) bool {
	if user != f.user {
		return false
	}
	return f.roles[repo] >= need
}

func withCookie(path string) *http.Request {
	req := httptest.NewRequest("GET", path, nil)
	req.AddCookie(&http.Cookie{Name: sessionCookie, Value: "good"})
	return req
}

// The fleet pages are gated at Admin with an empty repo. A user with
// write — even ADMIN — on a repository is not thereby a site admin, and
// the fleet surface exposes every store's operational detail. Getting
// this wrong turns "can edit one wiki" into "can read the whole fleet's
// error strings and force syncs across every store".
func TestFleetGateRefusesNonSiteAdmins(t *testing.T) {
	verify := Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })

	for _, tc := range []struct {
		name  string
		roles map[string]ops.Auth
		want  bool
	}{
		{"write on one repo", map[string]ops.Auth{"alpha": ops.Token}, false},
		{"admin on one repo", map[string]ops.Auth{"alpha": ops.Admin}, false},
		{"no roles at all", map[string]ops.Auth{}, false},
		{"site admin", map[string]ops.Auth{"": ops.Admin}, true},
	} {
		t.Run(tc.name, func(t *testing.T) {
			sess := fakeSessions{user: "someone", roles: tc.roles}
			w := httptest.NewRecorder()
			_, ok := gateAt(verify, sess, "", ops.Admin, w, withCookie("/fleet"))
			if ok != tc.want {
				t.Fatalf("gateAt admitted=%v, want %v (status %d)", ok, tc.want, w.Code)
			}
			if !tc.want && w.Code != http.StatusForbidden {
				t.Fatalf("refusal status = %d, want 403", w.Code)
			}
		})
	}
}

// A site admin must still be refused the per-repo write gate on a repo
// they hold no role for — gateAt must not have widened gate's meaning.
// (Site admins DO get every repo in the real role table; this asserts
// the level is what is consulted, not that admins are special-cased in
// the web layer.)
func TestGateStillAsksForTokenNotAdmin(t *testing.T) {
	verify := Verify(func(string, string, string, ops.Auth) api.Decision { return api.Denied })
	sess := fakeSessions{user: "someone", roles: map[string]ops.Auth{"alpha": ops.Token}}

	if _, ok := gate(verify, sess, "alpha", httptest.NewRecorder(), withCookie("/r/alpha/wiki/x/edit")); !ok {
		t.Fatal("token-level user refused a write page")
	}
	w := httptest.NewRecorder()
	if _, ok := gate(verify, sess, "beta", w, withCookie("/r/beta/wiki/x/edit")); ok {
		t.Fatal("user with no role on beta admitted to beta's write page")
	}
	if w.Code != http.StatusForbidden {
		t.Fatalf("refusal status = %d, want 403", w.Code)
	}
}

// The refusal message must not name a repository when the gate was not
// scoped to one: "someone has no write access to ”" is the shape that
// sends an operator hunting for a repo with an empty name.
func TestForbidWordsMatchTheGate(t *testing.T) {
	for _, tc := range []struct {
		repo  string
		level ops.Auth
		want  string
	}{
		{"", ops.Admin, "someone is not a site admin"},
		{"alpha", ops.Admin, "someone has no admin access to alpha"},
		{"alpha", ops.Token, "someone has no write access to alpha"},
	} {
		w := httptest.NewRecorder()
		forbid(w, "someone", tc.repo, tc.level)
		if got := w.Body.String(); got != tc.want {
			t.Errorf("forbid(%q, %v) = %q, want %q", tc.repo, tc.level, got, tc.want)
		}
	}
}

// The dependency banner RENDERS. It shipped once calling a template
// helper with the wrong type — an execution-time failure, so it built
// clean, passed every test that did not render it, and served a bare
// "template error" to the one page that reports outages. A panel that
// breaks when something is down is worse than no panel.
func TestFleetBannerRendersWhenADependencyIsDown(t *testing.T) {
	down := health.State{
		Dep:   health.Dep{Name: "notify", URL: "http://127.0.0.1:3000", Why: "no alerts are being delivered"},
		Up:    false,
		Since: time.Now().Add(-3 * time.Hour),
		Err:   "connection refused",
	}
	var buf bytes.Buffer
	err := noHooks.template(fleetAdminPage).Execute(&buf, pageData{Title: "Fleet", HideH1: true,
		FleetTab: "stores", DepsDown: []health.State{down}})
	if err != nil {
		t.Fatalf("fleet page does not render with a dependency down: %v", err)
	}
	html := buf.String()
	for _, want := range []string{"notify is down", "no alerts are being delivered",
		"connection refused", "3h0m"} {
		if !strings.Contains(html, want) {
			t.Errorf("banner does not say %q", want)
		}
	}
}

// Silence when everything is fine: a banner listing healthy services is
// a banner people learn to ignore.
func TestFleetBannerIsAbsentWhenHealthy(t *testing.T) {
	var buf bytes.Buffer
	if err := noHooks.template(fleetAdminPage).Execute(&buf, pageData{Title: "Fleet", HideH1: true, FleetTab: "stores"}); err != nil {
		t.Fatal(err)
	}
	if strings.Contains(buf.String(), "dep-down") {
		t.Error("a healthy forge is showing an outage banner")
	}
}