Skip to content
quarry Artifact [bfc1d20ed5]
Login

Artifact [bfc1d20ed5]

Artifact bfc1d20ed5b78e1a5d131dbafe8eebc4a103bc567cf3c52ad099d34d53eb953c:


package oltp

import (
	"reflect"
	"testing"
)

// An unprotected repository is every repository until an administrator
// adds a row: an imported fossil repository must behave exactly as it
// did. That is the compatibility promise, and it is the first thing to
// prove.
func TestNoRulesMeansEveryoneMayJoin(t *testing.T) {
	db := testDB(t)
	for _, user := range []string{"alice", "", "nobody-in-particular"} {
		if !db.MayJoin("alpha", user, "trunk") {
			t.Errorf("user %q refused on an unprotected repository", user)
		}
	}
	if r, err := db.Refused("alpha", "alice"); err != nil || len(r) != 0 {
		t.Errorf("refused=%v err=%v on an unprotected repository", r, err)
	}
}

func TestOwnersMayJoinAndOthersMayNot(t *testing.T) {
	db := testDB(t)
	if err := db.SetProtection("alpha", "trunk", []string{"alice", "bob"}); err != nil {
		t.Fatal(err)
	}
	if !db.MayJoin("alpha", "alice", "trunk") || !db.MayJoin("alpha", "bob", "trunk") {
		t.Error("an owner was refused")
	}
	if db.MayJoin("alpha", "carol", "trunk") {
		t.Error("a non-owner may join a protected branch")
	}
	// Nobody owns anything: an unauthenticated writer is not an owner
	// however the list is spelled.
	if db.MayJoin("alpha", "", "trunk") {
		t.Error("an empty user may join a protected branch")
	}
	// Other branches are untouched, and so are other repositories.
	if !db.MayJoin("alpha", "carol", "feature-x") {
		t.Error("protection leaked onto an unprotected branch")
	}
	if !db.MayJoin("beta", "carol", "trunk") {
		t.Error("protection leaked onto another repository")
	}
}

// release-* is the obvious need. Every matching rule applies, so a
// broader glob and a narrower name on one branch cannot be widened by
// adding a row — only narrowed.
func TestGlobRulesAndTheirIntersection(t *testing.T) {
	db := testDB(t)
	if err := db.SetProtection("alpha", "release-*", []string{"alice", "bob"}); err != nil {
		t.Fatal(err)
	}
	if err := db.SetProtection("alpha", "release-1.0", []string{"alice"}); err != nil {
		t.Fatal(err)
	}
	if !db.MayJoin("alpha", "bob", "release-2.0") {
		t.Error("bob owns release-* and was refused on release-2.0")
	}
	if db.MayJoin("alpha", "bob", "release-1.0") {
		t.Error("bob is not on release-1.0's own rule and was admitted")
	}
	if !db.MayJoin("alpha", "alice", "release-1.0") {
		t.Error("alice owns both rules and was refused")
	}
	if db.MayJoin("alpha", "carol", "release-9") {
		t.Error("carol matches release-* and was admitted")
	}
	// The per-request rule for the gate: every glob this account does
	// not own, whatever it would resolve to.
	got, err := db.Refused("alpha", "bob")
	if err != nil {
		t.Fatal(err)
	}
	if !reflect.DeepEqual(got, []string{"release-1.0"}) {
		t.Errorf("refused for bob = %v, want [release-1.0]", got)
	}
	got, _ = db.Refused("alpha", "carol")
	if !reflect.DeepEqual(got, []string{"release-*", "release-1.0"}) {
		t.Errorf("refused for carol = %v", got)
	}
}

// An empty owner list clears the row rather than creating a lock with
// no key; the settings form relies on that to delete a rule. Owners are
// normalised so the stored row reads the same however it was typed.
func TestSetProtectionNormalisesAndClears(t *testing.T) {
	db := testDB(t)
	if err := db.SetProtection("alpha", "trunk", []string{" bob  alice", "bob"}); err != nil {
		t.Fatal(err)
	}
	ps, err := db.Protections("alpha")
	if err != nil {
		t.Fatal(err)
	}
	if len(ps) != 1 || !reflect.DeepEqual(ps[0].Owners, []string{"alice", "bob"}) {
		t.Fatalf("stored %+v, want one rule with owners [alice bob]", ps)
	}
	if err := db.SetProtection("alpha", "trunk", nil); err != nil {
		t.Fatal(err)
	}
	if ps, _ := db.Protections("alpha"); len(ps) != 0 {
		t.Errorf("an empty owner list left a rule behind: %+v", ps)
	}
	if !db.MayJoin("alpha", "carol", "trunk") {
		t.Error("a cleared rule still refuses")
	}
}

// Rows are repository state and go with the repository, as caps do —
// on the purge, which is the cascade; DeleteRepo alone drops the row
// and leaves per-repo state for the purge to sweep.
func TestDeletingARepositoryDropsItsProtection(t *testing.T) {
	db := testDB(t)
	if err := db.SetProtection("alpha", "trunk", []string{"alice"}); err != nil {
		t.Fatal(err)
	}
	if err := db.PurgeRepo("alpha"); err != nil {
		t.Fatal(err)
	}
	if ps, _ := db.Protections("alpha"); len(ps) != 0 {
		t.Errorf("protection survived the repository: %+v", ps)
	}
}