Skip to content
quarry Artifact [e5198841de]
Login

Artifact [e5198841de]

Artifact e5198841de0a261418aa5512716100cb8957bba020cd1e0e55fedeae296ff1a7:


package oltp

import (
	"errors"
	"testing"
)

// A sync applies the categories and keeps everyone else as a list: no
// account is created, and a matching name is only suggested.
func TestFossilUsersAreAReferenceNotAccounts(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("bob", "a long password", false); err != nil {
		t.Fatal(err)
	}
	rows, cats, err := db.ReplaceFossilUsers("alpha", []FossilUser{
		{Login: "developer", Caps: "ei"},
		{Login: "alice", Caps: "ei", Info: "Alice Dev"},
		{Login: "bob", Caps: "k"},
	})
	if err != nil {
		t.Fatal(err)
	}
	if rows != 2 || cats != 1 {
		t.Fatalf("rows %d, categories %d", rows, cats)
	}
	if got := db.CategoryCaps("alpha", "developer"); got != "ei" {
		t.Fatalf("developer category = %q", got)
	}
	if _, ok := db.AccountByName("alice"); ok {
		t.Fatal("a sync created an account")
	}
	users, err := db.FossilUsers("alpha")
	if err != nil {
		t.Fatal(err)
	}
	if len(users) != 2 || users[0].Login != "alice" || users[0].Suggest || !users[1].Suggest {
		t.Fatalf("users = %+v", users)
	}
	if db.EffectiveCaps("bob", "alpha").HasAny("k") {
		t.Fatal("a matching name was granted without a link")
	}
}

// Only a link grants; a re-sync refreshes a linked account; a login
// that leaves fossil loses its row but not the account's access.
func TestFossilLinkGrantsAndSurvivesResync(t *testing.T) {
	db := testDB(t)
	if _, _, err := db.ReplaceFossilUsers("alpha", []FossilUser{{Login: "alice", Caps: "i"}}); err != nil {
		t.Fatal(err)
	}
	if err := linkErr(db, "alpha", "alice", "al"); !errors.Is(err, ErrNoAccount) {
		t.Fatalf("link to a missing account: %v", err)
	}
	if err := linkErr(db, "alpha", "nobody-here", ""); !errors.Is(err, ErrNoFossilUser) {
		t.Fatalf("link of a missing login: %v", err)
	}
	if err := db.AddAccount("al", "a long password", false); err != nil {
		t.Fatal(err)
	}
	if err := linkErr(db, "alpha", "alice", "al"); err != nil {
		t.Fatal(err)
	}
	if !db.EffectiveCaps("al", "alpha").HasAny("i") {
		t.Fatal("a link did not grant the login's capabilities")
	}
	if _, _, err := db.ReplaceFossilUsers("alpha", []FossilUser{{Login: "alice", Caps: "k"}}); err != nil {
		t.Fatal(err)
	}
	users, _ := db.FossilUsers("alpha")
	if len(users) != 1 || users[0].Linked != "al" || !db.EffectiveCaps("al", "alpha").HasAny("k") {
		t.Fatalf("a re-sync lost the link or the refresh: %+v", users)
	}
	if _, _, err := db.ReplaceFossilUsers("alpha", nil); err != nil {
		t.Fatal(err)
	}
	if users, _ := db.FossilUsers("alpha"); len(users) != 0 {
		t.Fatalf("a login gone from fossil kept its row: %+v", users)
	}
	if !db.EffectiveCaps("al", "alpha").HasAny("k") {
		t.Fatal("a sync revoked an account's access without an admin")
	}
}

// linkErr links as an actor the guard has nothing to say about here —
// none of these links touches an admin — for the tests about what a
// link does rather than who may make it.
func linkErr(db *DB, repo, login, account string) error {
	_, err := db.LinkFossilUser("root", repo, login, account)
	return err
}

// Fossil's setup and admin letters are admin here, and an owner's hand
// grant survives the next sync.
func TestFossilSetupLinksAsAdmin(t *testing.T) {
	for _, caps := range []string{"s", "a"} {
		if got := roleFromCaps(caps); got != RepoAdmin {
			t.Errorf("caps %q read as %v, want admin", caps, got)
		}
	}
	db := testDB(t)
	if err := db.AddAccount("dan", "a long password", false); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ReplaceFossilUsers("zit", []FossilUser{{Login: "dmestas", Caps: "s"}}); err != nil {
		t.Fatal(err)
	}
	if err := linkErr(db, "zit", "dmestas", "dan"); err != nil {
		t.Fatal(err)
	}
	g, _ := db.GrantsFor("dan")
	if g["zit"] != RepoAdmin {
		t.Fatalf("a linked setup user holds %v", g["zit"])
	}
}

// A rich string with no a or s is still the link's string, applied
// verbatim; it reads as no preset, and that reading is not a missing grant.
func TestFossilRichCapsApplyOnLink(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("ds", "a long password", false); err != nil {
		t.Fatal(err)
	}
	const rich = "bcfklmnqrvwxyz234567ACD"
	if _, _, err := db.ReplaceFossilUsers("zit", []FossilUser{{Login: "danshearer", Caps: rich}}); err != nil {
		t.Fatal(err)
	}
	if got, _ := db.CapsOf("ds", "zit"); got != "" {
		t.Fatalf("an unlinked row granted %q", got)
	}
	if err := linkErr(db, "zit", "danshearer", "ds"); err != nil {
		t.Fatal(err)
	}
	if got, _ := db.CapsOf("ds", "zit"); got != rich {
		t.Fatalf("link stored %q, want %q", got, rich)
	}
}

func TestResyncKeepsAHandGrant(t *testing.T) {
	db := testDB(t)
	if err := db.AddAccount("dan", "a long password", false); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ReplaceFossilUsers("zit", []FossilUser{{Login: "dmestas", Caps: "k"}}); err != nil {
		t.Fatal(err)
	}
	if err := linkErr(db, "zit", "dmestas", "dan"); err != nil {
		t.Fatal(err)
	}
	if err := db.Grant("dan", "zit", RepoAdmin); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ReplaceFossilUsers("zit", []FossilUser{{Login: "dmestas", Caps: "k"}}); err != nil {
		t.Fatal(err)
	}
	if got, _ := db.CapsOf("dan", "zit"); got != RepoAdmin.Caps() {
		t.Fatalf("a sync downgraded a hand grant to %q", got)
	}
	// Untouched since the last sync, an account still follows upstream.
	if err := db.AddAccount("al", "a long password", false); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ReplaceFossilUsers("zit", []FossilUser{{Login: "alice", Caps: "i"}}); err != nil {
		t.Fatal(err)
	}
	if err := linkErr(db, "zit", "alice", "al"); err != nil {
		t.Fatal(err)
	}
	if _, _, err := db.ReplaceFossilUsers("zit", []FossilUser{{Login: "alice", Caps: "k"}}); err != nil {
		t.Fatal(err)
	}
	if got, _ := db.CapsOf("al", "zit"); got != "k" {
		t.Fatalf("an untouched link stopped following: %q", got)
	}
}