Artifact bfc1d20ed5b78e1a5d131dbafe8eebc4a103bc567cf3c52ad099d34d53eb953c:
- File
internal/oltp/protect_test.go
— part of check-in
[f30535d269]
at
2026-08-26 22:55:35
on branch protect-1
— protect: the purge drops the rules, and the test asks the purge
The first test called DeleteRepo, which drops the repo row and leaves per-repository state to PurgeRepo — the cascade the rule was added to. Committed red because the run gating it used a regex that matched no test; caught by the suite. (user: dmestas, size: 4378) [annotate] [blame] [check-ins using] [more...]
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) } }