package api
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/oltp"
)
// account.explain's two halves must not contradict each other.
//
// The `can` sentence came from oltp.Explain, which asks HasAny and so
// reads `a` as a wildcard. The letter list came from oltp.Decode via
// Closure, which has no entry for `a`. Both were internally consistent
// and the ANSWER contradicted itself: a repository admin was told they
// could check in and edit tickets, above a list showing no i, k, w or
// f (8ea1e46d, G-84).
//
// Asserted at the op's own composition rather than only in oltp,
// because the defect was not in either function — it was in feeding
// them different inputs.
func TestExplainsSentenceAndLettersAgreeForAnAdmin(t *testing.T) {
const caps = "a"
sentence := oltp.Explain("jan", oltp.CapSet(caps))
// The composition the op performs. If this drifts from register.go
// the assertion below is worthless, so it is spelled the same way.
grants := oltp.Decode(oltp.AsGated(caps))
joined := strings.Join(grants, " | ")
for _, claim := range []string{"check in", "edit tickets", "edit wiki pages"} {
if !strings.Contains(sentence, claim) {
continue // the sentence does not make this claim; nothing to match
}
// The sentence claims it, so the letters must carry it.
var found bool
for _, g := range grants {
if strings.Contains(g, strings.TrimSuffix(claim, "s")) {
found = true
break
}
}
if !found {
t.Errorf("FAIL: the sentence says an admin can %q and the letter list does not "+
"carry it.\n sentence: %s\n letters: %s", claim, sentence, joined)
}
}
if len(grants) < 10 {
t.Errorf("FAIL: an admin's letter list has %d entries; `a` is not expanding, so the "+
"page will contradict its own sentence again", len(grants))
}
}
// A SITE admin's list matches its sentence too.
//
// This is the case my first fix missed and my first guard could not
// see. AsGated expanded `a` and not `s`, so explain told zeitgeist —
// who holds s — that they "can do everything" above a list of seven
// read-ish abilities. The same contradiction, one umbrella cap over,
// live on production after the fix that was supposed to end it.
//
// The guard in oltp could not catch it because it compares HasAny to
// HasAny, and HasAny has always expanded s: the invariant held
// trivially on both sides while the human-readable list was wrong.
// What was broken is what DECODE reports, so that is what this
// asserts.
func TestASiteAdminsLettersMatchItsSentence(t *testing.T) {
for _, caps := range []string{"s", "gjorzh2mncs"} {
sentence := oltp.Explain("zeitgeist", oltp.CapSet(caps))
grants := oltp.Decode(oltp.AsGated(caps))
if !strings.Contains(sentence, "everything") {
t.Fatalf("FAIL: with caps %q the sentence no longer claims everything (%q); this "+
"test is asserting nothing", caps, sentence)
}
// "Everything" has to mean everything: the list must carry an
// entry for every ability, x and y included, which is what s
// grants and a does not.
if len(grants) < len(oltp.Abilities)-2 {
t.Errorf("FAIL: caps %q says %q and lists only %d of %d abilities; the page "+
"contradicts itself", caps, "can do everything", len(grants), len(oltp.Abilities))
}
for _, claim := range []string{"see private branches", "push unversioned files"} {
var found bool
for _, g := range grants {
if strings.Contains(g, claim) {
found = true
break
}
}
if !found {
t.Errorf("FAIL: caps %q claims everything but its letters omit %q, which s "+
"grants", caps, claim)
}
}
}
}