package oltp
import (
"strings"
"testing"
)
// The sentence and the letters must answer the same question.
//
// account.explain gave one answer built from two rules. Its `can`
// sentence went through Explain, which asks CapSet.HasAny — the
// function every GATE goes through, and which reads `a` as a wildcard
// for everything except s, x and y. Its letter list went through
// Decode, which asks Closure, and Closure has no entry for `a` at all.
//
// So a repository admin was told they could check in and edit tickets,
// above a letter list showing no i, k, w or f. Both halves were
// internally consistent and the answer as a whole contradicted itself
// (8ea1e46d, G-84).
//
// This is the invariant: for any capability string, every ability the
// gate would PERMIT must be an ability the letters ADMIT. Stated over
// the abilities rather than over one example, so a new letter or a new
// implication cannot slip between the two halves again.
func TestWhatTheGatePermitsIsWhatTheLettersAdmit(t *testing.T) {
// The umbrella caps are FIRST, because leaving s out of this list is
// what let the s case ship broken: the invariant was right and the
// cases did not reach it.
for _, caps := range []string{
"s", "a", "as", "sx", "ax",
"", "i", "oi", "w", "k", "ogzh", "ai", "3", "C", "8",
} {
gated := CapSet(AsGated(caps))
for _, ab := range Abilities {
// What a gate would answer for this ability's own letter.
permits := CapSet(caps).HasAny(ab.Letters)
admits := gated.HasAny(ab.Letters)
if permits && !admits {
t.Errorf("FAIL: with caps %q a gate permits %q but the letters do not admit "+
"it; the sentence and the list disagree", caps, ab.Sentence)
}
// Both directions, now that dmestas has decided we
// follow fossil and HasAny consults the implies table.
//
// This assertion used to be impossible in one direction:
// HasAny knew the s and a wildcards and not the implies
// table, Closure knew implies and not the wildcards, so
// each admitted things the other refused. The letters
// admitted reads that gates denied (w without r) and the
// sentence claimed powers the letters omitted (a without
// i). One rule, so both hold.
if admits && !permits {
t.Errorf("FAIL: with caps %q the letters admit %q that no gate permits; the "+
"list overstates what the account may do", caps, ab.Sentence)
}
}
}
}
// And `a` expands to the gate's rule exactly: everything but s, x, y.
//
// Excluded deliberately and for different reasons: s is SITE admin and
// is not a repository letter at all, and fossil keeps private-branch
// (x) and unversioned-write (y) out of `a` on purpose. Getting this
// list wrong in the generous direction would have the page tell a
// repository admin they can read private branches.
func TestTheAdminLetterExpandsToTheGatesRuleAndNoFurther(t *testing.T) {
g := AsGated("a")
for _, l := range "sxy" {
if strings.ContainsRune(g, l) {
t.Errorf("FAIL: `a` expanded to include %q, which HasAny excludes; the page "+
"would claim a power no gate grants", l)
}
}
for _, want := range "iwkfn" {
if !strings.ContainsRune(g, want) {
t.Errorf("FAIL: `a` did not expand to %q, which HasAny grants", want)
}
}
// A non-admin set is untouched: this changes what an admin is TOLD,
// never what anybody else holds.
for _, caps := range []string{"i", "oi", "w", "ogzh", "3"} {
if AsGated(caps) != Closure(caps) {
t.Errorf("FAIL: caps %q changed under AsGated (%q vs %q); only `a` should expand",
caps, AsGated(caps), Closure(caps))
}
}
}