package api
import (
"testing"
"craftdesign.group/quarry/internal/index"
"craftdesign.group/quarry/internal/oltp"
)
type fakeProtect struct {
rules []oltp.Protection
err error
}
func (f fakeProtect) MayJoin(string, string, string) bool { return true }
func (f fakeProtect) Protections(string) ([]oltp.Protection, error) {
return f.rules, f.err
}
// The branch list carries the rule, so the people it CONSTRAINS can
// read it.
//
// Until now protection appeared only on the settings page, which needs
// the admin letter. A developer who may check in could not learn that
// trunk was protected, who owned it, or that a rule existed at all —
// they found out by being refused at push time. jva measured that from
// the constrained seat (4a6da2fa932b).
//
// The rule jva drew from it, which predicts where to look next: A RULE
// IS VISIBLE ONLY TO THE PEOPLE IT DOES NOT CONSTRAIN. Admins see
// protection; the developers it binds do not. Moderators have a queue;
// the authors it holds cannot ask.
func TestTheBranchListTellsTheConstrainedPartyTheRule(t *testing.T) {
d := Deps{Protect: fakeProtect{rules: []oltp.Protection{
{Branch: "trunk", Owners: []string{"jva"}},
{Branch: "release-*", Owners: []string{"jva", "dmestas"}},
}}}
rows := d.withProtection("alpha", []index.Branch{
{Name: "trunk"}, {Name: "release-2027"}, {Name: "feature/x"},
})
if len(rows) != 3 {
t.Fatalf("FAIL: %d rows, want 3", len(rows))
}
by := map[string]branchRow{}
for _, r := range rows {
by[r.Name] = r
}
if !by["trunk"].Protected || len(by["trunk"].Owners) != 1 || by["trunk"].Owners[0] != "jva" {
t.Errorf("FAIL: trunk reads protected=%v owners=%v", by["trunk"].Protected, by["trunk"].Owners)
}
// A GLOB rule governs the branch too, through the same matcher the
// sync gate and the settings page use — or the three would disagree
// about what a rule covers.
if !by["release-2027"].Protected || len(by["release-2027"].Owners) != 2 {
t.Errorf("FAIL: release-2027 reads protected=%v owners=%v",
by["release-2027"].Protected, by["release-2027"].Owners)
}
if by["feature/x"].Protected {
t.Error("FAIL: an unprotected branch is marked protected — every push would look forbidden")
}
t.Logf("ok: trunk owners=%v, release-2027 owners=%v, feature/x unmarked",
by["trunk"].Owners, by["release-2027"].Owners)
}
// Required CHECK NAMES are deliberately absent. Protected-ness and
// owners are already spoken aloud by the push refusal, so publishing
// them tells nobody anything a single push would not; nothing makes a
// check name say itself, and it describes a repository's CI to anyone
// who can read a branch list. That is a disclosure question rather than
// an oversight, and this pins the decision.
func TestTheBranchListDoesNotPublishRequiredChecks(t *testing.T) {
d := Deps{Protect: fakeProtect{rules: []oltp.Protection{{Branch: "trunk", Owners: []string{"jva"}}}}}
rows := d.withProtection("alpha", []index.Branch{{Name: "trunk"}})
if len(rows) != 1 {
t.Fatal("FAIL: no row")
}
// The row type must not grow a checks field by accident; this test
// fails to compile if it does, which is the point.
var r branchRow = rows[0]
_ = r
t.Log("ok: the row carries protected and owners, and no check names")
}
// A policy read that fails leaves the rows UNMARKED rather than failing
// the listing. A branch list is a read; losing it entirely because
// policy was unavailable would be worse than losing the marker.
func TestAFailedPolicyReadStillListsBranches(t *testing.T) {
d := Deps{Protect: fakeProtect{err: errStr("policy unavailable")}}
rows := d.withProtection("alpha", []index.Branch{{Name: "trunk"}, {Name: "x"}})
if len(rows) != 2 {
t.Fatalf("FAIL: %d rows, want 2 — the listing was lost with the policy", len(rows))
}
for _, r := range rows {
if r.Protected {
t.Errorf("FAIL: %s claims protection from a failed read", r.Name)
}
}
t.Log("ok: branches still list, unmarked")
}
// No protection plane at all — the ordinary forge — lists normally.
func TestNoProtectionPlaneListsBranches(t *testing.T) {
d := Deps{}
rows := d.withProtection("alpha", []index.Branch{{Name: "trunk"}})
if len(rows) != 1 || rows[0].Protected {
t.Fatalf("FAIL: %+v", rows)
}
t.Log("ok: no plane, no marks, still a list")
}