PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/permbook

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
permbook
Namespace
g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39
Files
3 (gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/g1qp5pt8cdq2f6kfdamn3yuxwg6quqnfpyewzz39/permbook
gno
0.9

Files (3)

  • gnomod.tomltoml
  • permbook.gnogno
  • permbook_test.gnogno
permbook_test.gnogno
1package permbook23import (4	"testing"56	"gno.land/p/nt/testutils/v0"7	"gno.land/p/nt/uassert/v0"8)910var (11	admin   = testutils.TestAddress("permadmin")12	nominee = testutils.TestAddress("nominee")13	alice   = testutils.TestAddress("alice")14	bob     = testutils.TestAddress("bob")15	mallory = testutils.TestAddress("mallory")16)1718// --- test harness ---19//20// permbook's mutators identify their principal as rlm.Previous().Address()21// after proving rlm.IsCurrent(). A test function's own `cur` is current but22// has no previous frame — Previous() panics with "cannot seek beyond origin23// caller override". So every mutating call is made from inside a real24// crossing frame, entered with cross(cur), exactly as p/nt/ownable/v0's own25// tests do.26//27// testing.SetRealm(testing.NewUserRealm(addr)) sets the current realm to a28// user realm for addr, so the next crossing frame has29// Previous().Address() == addr — the principal the mutator authorizes30// against. It is written out at every call site on purpose: the override is31// scoped to the frame that installs it, so wrapping it in a helper makes it32// pop the moment the helper returns and the principal comes back empty.3334func grant(cur realm, b *Book, perm string, addr address) error {35	var err error36	func(cur realm) { err = b.Grant(0, cur, perm, addr) }(cross(cur))37	return err38}3940func revoke(cur realm, b *Book, perm string, addr address) error {41	var err error42	func(cur realm) { err = b.Revoke(0, cur, perm, addr) }(cross(cur))43	return err44}4546func revokeAll(cur realm, b *Book, addr address) (int, error) {47	var (48		n   int49		err error50	)51	func(cur realm) { n, err = b.RevokeAll(0, cur, addr) }(cross(cur))52	return n, err53}5455func dropPerm(cur realm, b *Book, perm string) error {56	var err error57	func(cur realm) { err = b.DropPermission(0, cur, perm) }(cross(cur))58	return err59}6061func nominate(cur realm, b *Book, addr address) error {62	var err error63	func(cur realm) { err = b.NominateAdmin(0, cur, addr) }(cross(cur))64	return err65}6667func cancelNom(cur realm, b *Book) error {68	var err error69	func(cur realm) { err = b.CancelNomination(0, cur) }(cross(cur))70	return err71}7273func acceptAdmin(cur realm, b *Book) error {74	var err error75	func(cur realm) { err = b.AcceptAdmin(0, cur) }(cross(cur))76	return err77}7879// newBook builds a book for tests.80func newBook(t *testing.T, owner address) *Book {81	t.Helper()82	b, err := NewDefault(owner)83	uassert.NoError(t, err)84	if b == nil {85		t.Fatal("NewDefault returned nil book")86	}87	return b88}8990// --- construction ---9192func TestNewDefault(cur realm, t *testing.T) {93	b := newBook(t, admin)94	uassert.Equal(t, admin.String(), b.Admin().String())95	uassert.Equal(t, "", b.PendingAdmin().String())96	uassert.Equal(t, 0, b.PermissionCount())97	uassert.True(t, b.IsAdmin(admin))98	uassert.False(t, b.IsAdmin(alice))99100	l := b.Limits()101	uassert.Equal(t, DefaultMaxPermissions, l.MaxPermissions)102	uassert.Equal(t, DefaultMaxHoldersPerPermission, l.MaxHoldersPerPermission)103	uassert.Equal(t, DefaultMaxNameLen, l.MaxNameLen)104}105106func TestNewRejectsInvalidAdmin(cur realm, t *testing.T) {107	b, err := NewDefault(address(""))108	uassert.ErrorIs(t, err, ErrInvalidAddress)109	uassert.True(t, b == nil)110111	b, err = NewDefault(address("not-a-bech32-address"))112	uassert.ErrorIs(t, err, ErrInvalidAddress)113	uassert.True(t, b == nil)114}115116func TestNewRejectsInvalidLimits(cur realm, t *testing.T) {117	cases := []Limits{118		{MaxPermissions: 0, MaxHoldersPerPermission: 10, MaxNameLen: 10},119		{MaxPermissions: 10, MaxHoldersPerPermission: 0, MaxNameLen: 10},120		{MaxPermissions: 10, MaxHoldersPerPermission: 10, MaxNameLen: 0},121		{MaxPermissions: -1, MaxHoldersPerPermission: 10, MaxNameLen: 10},122		{MaxPermissions: MaxPermissionsCeiling + 1, MaxHoldersPerPermission: 10, MaxNameLen: 10},123		{MaxPermissions: 10, MaxHoldersPerPermission: MaxHoldersCeiling + 1, MaxNameLen: 10},124		{MaxPermissions: 10, MaxHoldersPerPermission: 10, MaxNameLen: MaxNameLenCeiling + 1},125	}126	for i, lim := range cases {127		b, err := New(admin, lim)128		uassert.ErrorIs(t, err, ErrInvalidLimits)129		if b != nil {130			t.Errorf("case %d: expected nil book", i)131		}132	}133}134135func TestNewAcceptsCeilingLimits(cur realm, t *testing.T) {136	b, err := New(admin, Limits{137		MaxPermissions:          MaxPermissionsCeiling,138		MaxHoldersPerPermission: MaxHoldersCeiling,139		MaxNameLen:              MaxNameLenCeiling,140	})141	uassert.NoError(t, err)142	uassert.Equal(t, MaxPermissionsCeiling, b.Limits().MaxPermissions)143}144145// --- grant / query / revoke, the core objective ---146147func TestGrantThenHas(cur realm, t *testing.T) {148	testing.SetRealm(testing.NewUserRealm(admin))149	b := newBook(t, admin)150151	uassert.False(t, b.Has("withdraw", alice))152	uassert.NoError(t, grant(cur, b, "withdraw", alice))153	uassert.True(t, b.Has("withdraw", alice))154155	// granting to alice does not grant to bob156	uassert.False(t, b.Has("withdraw", bob))157	// nor does it create any other permission158	uassert.False(t, b.Has("deposit", alice))159	uassert.Equal(t, 1, b.PermissionCount())160	uassert.Equal(t, 1, b.HolderCount("withdraw"))161}162163func TestRevokeRemovesTheGrant(cur realm, t *testing.T) {164	testing.SetRealm(testing.NewUserRealm(admin))165	b := newBook(t, admin)166167	uassert.NoError(t, grant(cur, b, "withdraw", alice))168	uassert.NoError(t, grant(cur, b, "withdraw", bob))169	uassert.True(t, b.Has("withdraw", alice))170171	uassert.NoError(t, revoke(cur, b, "withdraw", alice))172	uassert.False(t, b.Has("withdraw", alice))173	// bob is untouched174	uassert.True(t, b.Has("withdraw", bob))175	uassert.Equal(t, 1, b.HolderCount("withdraw"))176	// the permission still exists because bob holds it177	uassert.Equal(t, 1, b.PermissionCount())178}179180func TestRevokingLastHolderPrunesThePermission(cur realm, t *testing.T) {181	testing.SetRealm(testing.NewUserRealm(admin))182	b := newBook(t, admin)183184	uassert.NoError(t, grant(cur, b, "withdraw", alice))185	uassert.Equal(t, 1, b.PermissionCount())186187	uassert.NoError(t, revoke(cur, b, "withdraw", alice))188	// the empty bucket is gone, so it does not consume a MaxPermissions slot189	uassert.Equal(t, 0, b.PermissionCount())190	uassert.Equal(t, 0, b.HolderCount("withdraw"))191	// and the query is still well-defined192	uassert.False(t, b.Has("withdraw", alice))193}194195func TestGrantAfterPruneWorks(cur realm, t *testing.T) {196	testing.SetRealm(testing.NewUserRealm(admin))197	b := newBook(t, admin)198199	uassert.NoError(t, grant(cur, b, "withdraw", alice))200	uassert.NoError(t, revoke(cur, b, "withdraw", alice))201	// re-creating a pruned permission must not collide with a stale bucket202	uassert.NoError(t, grant(cur, b, "withdraw", bob))203	uassert.True(t, b.Has("withdraw", bob))204	uassert.False(t, b.Has("withdraw", alice))205	uassert.Equal(t, 1, b.PermissionCount())206}207208func TestDuplicateGrantIsAnError(cur realm, t *testing.T) {209	testing.SetRealm(testing.NewUserRealm(admin))210	b := newBook(t, admin)211212	uassert.NoError(t, grant(cur, b, "withdraw", alice))213	uassert.ErrorIs(t, grant(cur, b, "withdraw", alice), ErrAlreadyGranted)214	// the failed duplicate did not grow state215	uassert.Equal(t, 1, b.HolderCount("withdraw"))216}217218func TestRevokeWhatIsNotHeld(cur realm, t *testing.T) {219	testing.SetRealm(testing.NewUserRealm(admin))220	b := newBook(t, admin)221222	// unknown permission223	uassert.ErrorIs(t, revoke(cur, b, "nope", alice), ErrNotGranted)224	// known permission, wrong holder225	uassert.NoError(t, grant(cur, b, "withdraw", alice))226	uassert.ErrorIs(t, revoke(cur, b, "withdraw", bob), ErrNotGranted)227	uassert.True(t, b.Has("withdraw", alice))228}229230func TestMultiplePermissionsAreIndependent(cur realm, t *testing.T) {231	testing.SetRealm(testing.NewUserRealm(admin))232	b := newBook(t, admin)233234	uassert.NoError(t, grant(cur, b, "read", alice))235	uassert.NoError(t, grant(cur, b, "write", alice))236	uassert.NoError(t, grant(cur, b, "write", bob))237238	uassert.True(t, b.Has("read", alice))239	uassert.True(t, b.Has("write", alice))240	uassert.False(t, b.Has("read", bob))241	uassert.True(t, b.Has("write", bob))242	uassert.Equal(t, 2, b.PermissionCount())243244	uassert.NoError(t, revoke(cur, b, "write", alice))245	// revoking one permission leaves the other intact246	uassert.True(t, b.Has("read", alice))247	uassert.False(t, b.Has("write", alice))248	uassert.True(t, b.Has("write", bob))249}250251// --- authorization ---252253func TestNonAdminCannotGrant(cur realm, t *testing.T) {254	testing.SetRealm(testing.NewUserRealm(mallory))255	b := newBook(t, admin)256257	uassert.ErrorIs(t, grant(cur, b, "withdraw", mallory), ErrUnauthorized)258	uassert.False(t, b.Has("withdraw", mallory))259	uassert.Equal(t, 0, b.PermissionCount())260}261262func TestNonAdminCannotRevoke(cur realm, t *testing.T) {263	testing.SetRealm(testing.NewUserRealm(admin))264	b := newBook(t, admin)265	uassert.NoError(t, grant(cur, b, "withdraw", alice))266267	testing.SetRealm(testing.NewUserRealm(mallory))268	uassert.ErrorIs(t, revoke(cur, b, "withdraw", alice), ErrUnauthorized)269	uassert.True(t, b.Has("withdraw", alice))270}271272// A holder of a permission has no authority over the book. This is the273// property that p/thox/accesscontrol loses by exporting Role.Holders and274// returning *Role: there, any holder can self-grant.275func TestHolderIsNotAdmin(cur realm, t *testing.T) {276	testing.SetRealm(testing.NewUserRealm(admin))277	b := newBook(t, admin)278	uassert.NoError(t, grant(cur, b, "withdraw", alice))279280	testing.SetRealm(testing.NewUserRealm(alice))281	uassert.ErrorIs(t, grant(cur, b, "withdraw", mallory), ErrUnauthorized)282	uassert.ErrorIs(t, revoke(cur, b, "withdraw", alice), ErrUnauthorized)283	uassert.ErrorIs(t, dropPerm(cur, b, "withdraw"), ErrUnauthorized)284	uassert.ErrorIs(t, nominate(cur, b, alice), ErrUnauthorized)285	_, err := revokeAll(cur, b, alice)286	uassert.ErrorIs(t, err, ErrUnauthorized)287288	uassert.False(t, b.Has("withdraw", mallory))289	uassert.True(t, b.Has("withdraw", alice))290	uassert.Equal(t, admin.String(), b.Admin().String())291}292293func TestNonAdminCannotDropOrRevokeAll(cur realm, t *testing.T) {294	testing.SetRealm(testing.NewUserRealm(admin))295	b := newBook(t, admin)296	uassert.NoError(t, grant(cur, b, "a", alice))297	uassert.NoError(t, grant(cur, b, "b", alice))298299	testing.SetRealm(testing.NewUserRealm(mallory))300	uassert.ErrorIs(t, dropPerm(cur, b, "a"), ErrUnauthorized)301	n, err := revokeAll(cur, b, alice)302	uassert.ErrorIs(t, err, ErrUnauthorized)303	uassert.Equal(t, 0, n)304	uassert.Equal(t, 2, b.PermissionCount())305}306307// --- name validation ---308309func TestInvalidPermissionNames(cur realm, t *testing.T) {310	testing.SetRealm(testing.NewUserRealm(admin))311	b := newBook(t, admin)312313	bad := []string{314		"",           // empty315		"Withdraw",   // uppercase316		"with draw",  // space317		"with-draw",  // hyphen318		"with.draw",  // dot319		"with/draw",  // path separator320		"with,draw",  // comma — a delimiter in rendered lists321		"[link](x)",  // markdown322		"with\ndraw", // newline323		"withdraw!",  // punctuation324		"wíthdraw",   // non-ascii325	}326	for _, name := range bad {327		uassert.ErrorIs(t, grant(cur, b, name, alice), ErrInvalidName)328	}329	uassert.Equal(t, 0, b.PermissionCount())330331	good := []string{"a", "withdraw", "with_draw", "perm2", "_leading", "trailing_"}332	for _, name := range good {333		uassert.NoError(t, grant(cur, b, name, alice))334	}335	uassert.Equal(t, len(good), b.PermissionCount())336}337338func TestNameLengthBound(cur realm, t *testing.T) {339	testing.SetRealm(testing.NewUserRealm(admin))340	b, err := New(admin, Limits{MaxPermissions: 8, MaxHoldersPerPermission: 8, MaxNameLen: 4})341	uassert.NoError(t, err)342343	uassert.NoError(t, grant(cur, b, "abcd", alice))344	uassert.ErrorIs(t, grant(cur, b, "abcde", alice), ErrInvalidName)345	uassert.Equal(t, 1, b.PermissionCount())346}347348func TestGrantRejectsInvalidAddress(cur realm, t *testing.T) {349	testing.SetRealm(testing.NewUserRealm(admin))350	b := newBook(t, admin)351352	uassert.ErrorIs(t, grant(cur, b, "withdraw", address("")), ErrInvalidAddress)353	uassert.ErrorIs(t, grant(cur, b, "withdraw", address("g1nonsense")), ErrInvalidAddress)354	// a rejected grant must not have created the permission bucket355	uassert.Equal(t, 0, b.PermissionCount())356}357358// --- bounds ---359360func TestPermissionLimit(cur realm, t *testing.T) {361	testing.SetRealm(testing.NewUserRealm(admin))362	b, err := New(admin, Limits{MaxPermissions: 3, MaxHoldersPerPermission: 8, MaxNameLen: 16})363	uassert.NoError(t, err)364365	uassert.NoError(t, grant(cur, b, "p1", alice))366	uassert.NoError(t, grant(cur, b, "p2", alice))367	uassert.NoError(t, grant(cur, b, "p3", alice))368	uassert.ErrorIs(t, grant(cur, b, "p4", alice), ErrPermissionLimit)369	uassert.Equal(t, 3, b.PermissionCount())370371	// granting an EXISTING permission to a new address is not blocked by the372	// permission limit373	uassert.NoError(t, grant(cur, b, "p1", bob))374375	// freeing a slot re-opens it376	uassert.NoError(t, dropPerm(cur, b, "p3"))377	uassert.NoError(t, grant(cur, b, "p4", alice))378	uassert.Equal(t, 3, b.PermissionCount())379}380381func TestHolderLimit(cur realm, t *testing.T) {382	testing.SetRealm(testing.NewUserRealm(admin))383	b, err := New(admin, Limits{MaxPermissions: 4, MaxHoldersPerPermission: 2, MaxNameLen: 16})384	uassert.NoError(t, err)385386	uassert.NoError(t, grant(cur, b, "p", alice))387	uassert.NoError(t, grant(cur, b, "p", bob))388	uassert.ErrorIs(t, grant(cur, b, "p", mallory), ErrHolderLimit)389	uassert.Equal(t, 2, b.HolderCount("p"))390	uassert.False(t, b.Has("p", mallory))391392	// a revoke frees a slot393	uassert.NoError(t, revoke(cur, b, "p", alice))394	uassert.NoError(t, grant(cur, b, "p", mallory))395	uassert.True(t, b.Has("p", mallory))396}397398// A rejected grant must leave no bucket behind that occupies a399// MaxPermissions slot without holding anything.400//401// Two rejection routes exist and they are NOT the same case. A holder-limit402// rejection can only happen on a permission that already had holders, so403// nothing was created to clean up — Grant documents that argument where it404// returns ErrHolderLimit. An invalid-name or invalid-address rejection405// happens before any bucket is created. This asserts the invariant on both406// routes rather than on a branch.407func TestFailedGrantLeavesNoEmptyBucket(cur realm, t *testing.T) {408	testing.SetRealm(testing.NewUserRealm(admin))409	b, err := New(admin, Limits{MaxPermissions: 2, MaxHoldersPerPermission: 1, MaxNameLen: 16})410	uassert.NoError(t, err)411412	// route 1: holder limit, on a permission that already has its holder413	uassert.NoError(t, grant(cur, b, "p1", alice))414	uassert.ErrorIs(t, grant(cur, b, "p1", bob), ErrHolderLimit)415	// p1 still has exactly its one holder and no second bucket appeared416	uassert.Equal(t, 1, b.PermissionCount())417	uassert.Equal(t, 1, b.HolderCount("p1"))418	uassert.True(t, b.Has("p1", alice))419	uassert.False(t, b.Has("p1", bob))420421	// route 2: a rejection that happens before creation. The one free slot422	// is still free afterwards, which is what proves nothing was consumed.423	uassert.ErrorIs(t, grant(cur, b, "BAD", alice), ErrInvalidName)424	uassert.ErrorIs(t, grant(cur, b, "p2", address("")), ErrInvalidAddress)425	uassert.Equal(t, 1, b.PermissionCount())426	uassert.NoError(t, grant(cur, b, "p2", alice))427	uassert.Equal(t, 2, b.PermissionCount())428}429430// --- RevokeAll and DropPermission ---431432func TestRevokeAll(cur realm, t *testing.T) {433	testing.SetRealm(testing.NewUserRealm(admin))434	b := newBook(t, admin)435436	uassert.NoError(t, grant(cur, b, "read", alice))437	uassert.NoError(t, grant(cur, b, "write", alice))438	uassert.NoError(t, grant(cur, b, "admin_panel", alice))439	uassert.NoError(t, grant(cur, b, "read", bob))440441	n, err := revokeAll(cur, b, alice)442	uassert.NoError(t, err)443	uassert.Equal(t, 3, n)444445	uassert.False(t, b.HasAny(alice))446	uassert.False(t, b.Has("read", alice))447	uassert.False(t, b.Has("write", alice))448	uassert.False(t, b.Has("admin_panel", alice))449450	// bob is untouched, and "read" survives because he still holds it451	uassert.True(t, b.Has("read", bob))452	// "write" and "admin_panel" were emptied and therefore pruned453	uassert.Equal(t, 1, b.PermissionCount())454}455456func TestRevokeAllOnAddressWithNothing(cur realm, t *testing.T) {457	testing.SetRealm(testing.NewUserRealm(admin))458	b := newBook(t, admin)459	uassert.NoError(t, grant(cur, b, "read", bob))460461	n, err := revokeAll(cur, b, alice)462	uassert.NoError(t, err)463	uassert.Equal(t, 0, n)464	uassert.True(t, b.Has("read", bob))465}466467func TestDropPermission(cur realm, t *testing.T) {468	testing.SetRealm(testing.NewUserRealm(admin))469	b := newBook(t, admin)470471	uassert.NoError(t, grant(cur, b, "read", alice))472	uassert.NoError(t, grant(cur, b, "read", bob))473	uassert.NoError(t, grant(cur, b, "write", alice))474475	uassert.NoError(t, dropPerm(cur, b, "read"))476	uassert.False(t, b.Has("read", alice))477	uassert.False(t, b.Has("read", bob))478	uassert.Equal(t, 0, b.HolderCount("read"))479	// the other permission survives480	uassert.True(t, b.Has("write", alice))481	uassert.Equal(t, 1, b.PermissionCount())482483	uassert.ErrorIs(t, dropPerm(cur, b, "read"), ErrUnknownPermission)484	uassert.ErrorIs(t, dropPerm(cur, b, "never_existed"), ErrUnknownPermission)485}486487// --- admin handoff ---488489func TestTwoStepAdminHandoff(cur realm, t *testing.T) {490	testing.SetRealm(testing.NewUserRealm(admin))491	b := newBook(t, admin)492493	uassert.NoError(t, nominate(cur, b, nominee))494	// nomination alone does NOT move the admin495	uassert.Equal(t, admin.String(), b.Admin().String())496	uassert.Equal(t, nominee.String(), b.PendingAdmin().String())497	// and the incumbent still has authority498	uassert.NoError(t, grant(cur, b, "read", alice))499500	// the nominee cannot yet act501	testing.SetRealm(testing.NewUserRealm(nominee))502	uassert.ErrorIs(t, grant(cur, b, "write", alice), ErrUnauthorized)503504	uassert.NoError(t, acceptAdmin(cur, b))505	uassert.Equal(t, nominee.String(), b.Admin().String())506	uassert.Equal(t, "", b.PendingAdmin().String())507	uassert.NoError(t, grant(cur, b, "write", alice))508509	// the former admin has lost authority510	testing.SetRealm(testing.NewUserRealm(admin))511	uassert.ErrorIs(t, grant(cur, b, "other", alice), ErrUnauthorized)512513	// grants made under the previous admin survive the handoff514	uassert.True(t, b.Has("read", alice))515}516517func TestOnlyNomineeCanAccept(cur realm, t *testing.T) {518	testing.SetRealm(testing.NewUserRealm(admin))519	b := newBook(t, admin)520	uassert.NoError(t, nominate(cur, b, nominee))521522	testing.SetRealm(testing.NewUserRealm(mallory))523	uassert.ErrorIs(t, acceptAdmin(cur, b), ErrNotPendingAdmin)524	uassert.Equal(t, admin.String(), b.Admin().String())525526	// not even the incumbent admin can accept on the nominee's behalf527	testing.SetRealm(testing.NewUserRealm(admin))528	uassert.ErrorIs(t, acceptAdmin(cur, b), ErrNotPendingAdmin)529	uassert.Equal(t, admin.String(), b.Admin().String())530}531532func TestAcceptWithoutNomination(cur realm, t *testing.T) {533	testing.SetRealm(testing.NewUserRealm(nominee))534	b := newBook(t, admin)535	uassert.ErrorIs(t, acceptAdmin(cur, b), ErrNoPendingAdmin)536	uassert.Equal(t, admin.String(), b.Admin().String())537}538539func TestCancelNomination(cur realm, t *testing.T) {540	testing.SetRealm(testing.NewUserRealm(admin))541	b := newBook(t, admin)542543	uassert.ErrorIs(t, cancelNom(cur, b), ErrNoPendingAdmin)544545	uassert.NoError(t, nominate(cur, b, nominee))546	uassert.NoError(t, cancelNom(cur, b))547	uassert.Equal(t, "", b.PendingAdmin().String())548549	// the withdrawn nominee can no longer accept550	testing.SetRealm(testing.NewUserRealm(nominee))551	uassert.ErrorIs(t, acceptAdmin(cur, b), ErrNoPendingAdmin)552	uassert.Equal(t, admin.String(), b.Admin().String())553}554555func TestRenominationReplacesPending(cur realm, t *testing.T) {556	testing.SetRealm(testing.NewUserRealm(admin))557	b := newBook(t, admin)558559	uassert.NoError(t, nominate(cur, b, nominee))560	uassert.NoError(t, nominate(cur, b, bob))561	uassert.Equal(t, bob.String(), b.PendingAdmin().String())562563	// the superseded nominee cannot accept564	testing.SetRealm(testing.NewUserRealm(nominee))565	uassert.ErrorIs(t, acceptAdmin(cur, b), ErrNotPendingAdmin)566567	testing.SetRealm(testing.NewUserRealm(bob))568	uassert.NoError(t, acceptAdmin(cur, b))569	uassert.Equal(t, bob.String(), b.Admin().String())570}571572func TestNominateRejectsBadAddress(cur realm, t *testing.T) {573	testing.SetRealm(testing.NewUserRealm(admin))574	b := newBook(t, admin)575576	uassert.ErrorIs(t, nominate(cur, b, address("")), ErrInvalidAddress)577	uassert.ErrorIs(t, nominate(cur, b, address("g1bogus")), ErrInvalidAddress)578	uassert.ErrorIs(t, nominate(cur, b, admin), ErrSameAdmin)579	uassert.Equal(t, "", b.PendingAdmin().String())580}581582// --- read-side queries ---583584func TestPermissionsOfAddress(cur realm, t *testing.T) {585	testing.SetRealm(testing.NewUserRealm(admin))586	b := newBook(t, admin)587588	uassert.Equal(t, 0, len(b.Permissions(alice)))589590	uassert.NoError(t, grant(cur, b, "write", alice))591	uassert.NoError(t, grant(cur, b, "audit", alice))592	uassert.NoError(t, grant(cur, b, "read", alice))593	uassert.NoError(t, grant(cur, b, "read", bob))594595	got := b.Permissions(alice)596	uassert.Equal(t, 3, len(got))597	// lexicographic order, which is what makes rendered output deterministic598	uassert.Equal(t, "audit", got[0])599	uassert.Equal(t, "read", got[1])600	uassert.Equal(t, "write", got[2])601602	uassert.Equal(t, 1, len(b.Permissions(bob)))603	uassert.Equal(t, 0, len(b.Permissions(mallory)))604}605606func TestHasAny(cur realm, t *testing.T) {607	testing.SetRealm(testing.NewUserRealm(admin))608	b := newBook(t, admin)609610	uassert.False(t, b.HasAny(alice))611	uassert.NoError(t, grant(cur, b, "read", alice))612	uassert.True(t, b.HasAny(alice))613	uassert.False(t, b.HasAny(bob))614615	uassert.NoError(t, revoke(cur, b, "read", alice))616	uassert.False(t, b.HasAny(alice))617}618619func TestPermissionNamesPagination(cur realm, t *testing.T) {620	testing.SetRealm(testing.NewUserRealm(admin))621	b := newBook(t, admin)622623	for _, p := range []string{"e", "a", "d", "b", "c"} {624		uassert.NoError(t, grant(cur, b, p, alice))625	}626627	all := b.PermissionNames(0, 10)628	uassert.Equal(t, 5, len(all))629	uassert.Equal(t, "a", all[0])630	uassert.Equal(t, "e", all[4])631632	page := b.PermissionNames(1, 2)633	uassert.Equal(t, 2, len(page))634	uassert.Equal(t, "b", page[0])635	uassert.Equal(t, "c", page[1])636637	uassert.Equal(t, 0, len(b.PermissionNames(0, 0)))638	uassert.Equal(t, 0, len(b.PermissionNames(0, -1)))639	uassert.Equal(t, 0, len(b.PermissionNames(99, 5)))640}641642func TestHoldersPagination(cur realm, t *testing.T) {643	testing.SetRealm(testing.NewUserRealm(admin))644	b := newBook(t, admin)645646	uassert.NoError(t, grant(cur, b, "read", alice))647	uassert.NoError(t, grant(cur, b, "read", bob))648	uassert.NoError(t, grant(cur, b, "read", mallory))649650	all := b.Holders("read", 0, 10)651	uassert.Equal(t, 3, len(all))652653	page := b.Holders("read", 0, 2)654	uassert.Equal(t, 2, len(page))655	// sorted order, so pages do not overlap or skip656	uassert.True(t, page[0].String() < page[1].String())657658	uassert.Equal(t, 0, len(b.Holders("read", 0, 0)))659	uassert.Equal(t, 0, len(b.Holders("nope", 0, 10)))660}661662// Queries perform no authentication at all — consumer-contract clause 3.663// Anyone may read; that is deliberate and is asserted so a future change664// that adds a gate is caught as a behavioral break.665func TestQueriesAreUnauthenticated(cur realm, t *testing.T) {666	testing.SetRealm(testing.NewUserRealm(admin))667	b := newBook(t, admin)668	uassert.NoError(t, grant(cur, b, "read", alice))669670	testing.SetRealm(testing.NewUserRealm(mallory))671	uassert.True(t, b.Has("read", alice))672	uassert.Equal(t, 1, b.HolderCount("read"))673	uassert.Equal(t, 1, len(b.Permissions(alice)))674	uassert.Equal(t, admin.String(), b.Admin().String())675}676677// --- the groups base set is never used ---678679// permbook never writes to the Group's base address set, so "in the base680// set" can never be confused with "holds a permission". HasAny consults681// base OR roles; if base were ever written, this would report true.682func TestBaseSetStaysEmpty(cur realm, t *testing.T) {683	testing.SetRealm(testing.NewUserRealm(admin))684	b := newBook(t, admin)685686	uassert.NoError(t, grant(cur, b, "read", alice))687	uassert.NoError(t, revoke(cur, b, "read", alice))688689	// with every role gone, HasAny can only be true via the base set690	uassert.Equal(t, 0, b.PermissionCount())691	uassert.False(t, b.HasAny(alice))692	uassert.False(t, b.HasAny(admin))693}694

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.