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": "u3bBC7",
"Developer": "uv3bBC7",
"Maintainer": "uv45bBlqCA7",
"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("uv3bB7") // 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("u3bBC7") {
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
}