Skip to content
quarry Artifact [7599cfd590]
Login

Artifact [7599cfd590]

Artifact 7599cfd590f2752fc92474868cd814157aadb736d950c44c6af7fea6b41dcb16:


package oltp

import (
	"strings"
	"testing"
)

// The presets are the RFC's most consequential decision, so each letter
// set is asserted, not described.
func TestPresetsAreExactlyWhatTheRFCSays(t *testing.T) {
	want := map[string]string{
		"Reader":     "u3bBC78",
		"Developer":  "uv3bBC78",
		"Maintainer": "uv45bBlqCA78",
		"Admin":      "a",
	}
	if len(Presets) != len(want) {
		t.Fatalf("%d presets, want %d", len(Presets), len(want))
	}
	for _, p := range Presets {
		if p.Caps != want[p.Name] {
			t.Errorf("%s = %q, want %q", p.Name, p.Caps, want[p.Name])
		}
		if PresetOf(p.Caps) != p.Name {
			t.Errorf("%s does not read back as itself", p.Name)
		}
	}
	// The write role's gap, closed: a Developer can post and chat.
	dev := CapSet(Closure("uv3bBC7"))
	if !dev.HasAny("3") || !dev.HasAny("C") || !dev.HasAny("b") {
		t.Error("a Developer cannot post, chat, or attach")
	}
}

// Fossil's implications: two strings it treats alike compare alike.
func TestClosureFollowsFossilsImplications(t *testing.T) {
	for in, want := range map[string]string{
		"w":   "cnrw",
		"5":   "2345",
		"k":   "jkm",
		"i":   "io",
		"6":   "23456",
		"uv":  "uv",
		"a b": "ab",
	} {
		if got := Closure(in); got != want {
			t.Errorf("Closure(%q) = %q, want %q", in, got, want)
		}
	}
}

// A string that is a role plus a little reads as that role plus the
// difference, in sentences — the chip the People table draws.
func TestInferReadsARoleAndItsDifference(t *testing.T) {
	rd := Infer("uv3bBC75")
	if rd.Role != "Developer" || rd.Exact || len(rd.Plus) == 0 {
		t.Fatalf("Developer + moderate read as %+v", rd)
	}
	if rd.Plus[0].Sentence != "post without moderation" && rd.Plus[len(rd.Plus)-1].Sentence != "moderate the forum" {
		t.Errorf("plus = %v", sentences(rd.Plus))
	}
	rd = Infer("uv3bB78") // Developer minus chat
	if rd.Role != "Developer" || len(rd.Minus) != 1 || rd.Minus[0].Sentence != "chat" {
		t.Errorf("Developer minus chat read as %+v", rd)
	}
	if rd := Infer("a"); rd.Role != "Admin" || !rd.Exact {
		t.Errorf("a read as %+v", rd)
	}
	// Far from everything: Custom, not a misleading nearest.
	if rd := Infer("iy"); rd.Role != "Custom" {
		t.Errorf("a bot's iy read as %q", rd.Role)
	}
}

// Compile is the editor: a radio and checkboxes become the string, and
// the string reads back as the same radio and checkboxes.
func TestCompileRoundTripsThroughInfer(t *testing.T) {
	s := Compile("Developer", []string{"moderate the forum"}, []string{"chat"})
	rd := Infer(s)
	if rd.Role != "Developer" || len(rd.Plus) < 1 || len(rd.Minus) != 1 {
		t.Fatalf("compiled %q reads as %+v", s, rd)
	}
	if Compile("Nope", nil, nil) != "" {
		t.Error("an unknown role compiled to something")
	}
	if Compile("Reader", nil, nil) != Closure("u3bBC78") {
		t.Error("Reader alone does not compile to its preset")
	}
}

// Every letter in a string is accounted for in its decoding: nothing a
// person typed is silently dropped from the explanation.
func TestDecodeAccountsForEveryLetter(t *testing.T) {
	got := strings.Join(Decode("uv3bBC7xD"), " | ")
	for _, want := range []string{"post in the forum", "chat", "see private branches", "reader category", "developer category"} {
		if !strings.Contains(got, want) {
			t.Errorf("decode lacks %q:\n%s", want, got)
		}
	}
	if got := Decode("Z"); len(got) != 1 || got[0] != "letter Z" {
		t.Errorf("an unknown letter vanished: %v", got)
	}
}

// The sentence the People table shows, computed from the EFFECTIVE set.
func TestExplainSaysWhatAPersonCanAndCannotDo(t *testing.T) {
	db := testDB(t)
	db.SetCaps("bob", "alpha", "uv")
	got := Explain("bob", db.EffectiveCaps("bob", "alpha"))
	if !strings.Contains(got, "check in") || !strings.Contains(got, "but not") || !strings.Contains(got, "post in the forum") {
		t.Errorf("the old write role's gap is not stated: %q", got)
	}
	db.SetCaps("bob", "alpha", Compile("Developer", nil, nil))
	got = Explain("bob", db.EffectiveCaps("bob", "alpha"))
	if strings.Contains(got, "not post in the forum") || !strings.Contains(got, "chat") {
		t.Errorf("a Developer is still described as unable to talk: %q", got)
	}
	if got := Explain("dan", CapSet("s")); !strings.Contains(got, "site administrator") {
		t.Errorf("%q", got)
	}
}

// Visibility is a preset over the two category strings, and reads back.
func TestVisibilityPresetsRoundTrip(t *testing.T) {
	for _, v := range Visibilities {
		if VisibilityOf(v.Nobody, v.Anonymous) != v.Name {
			t.Errorf("%s does not read back", v.Name)
		}
	}
	if VisibilityOf(StockNobody, StockAnonymous) != "Custom" {
		t.Error("fossil's stock defaults read as a preset; they differ from Public by design")
	}
	if VisibilityOf("", "") != "Private" {
		t.Error("blank categories are not Private")
	}
}

func sentences(as []Ability) []string {
	out := make([]string, len(as))
	for i, a := range as {
		out[i] = a.Sentence
	}
	return out
}