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/nt/groups/v0

Package
Open in gnoweb ↗

Overview

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

Files (8)

  • README.mdmarkdown
  • gnomod.tomltoml
  • doc.gnogno
  • group.gnogno
  • readonly.gnogno
  • role.gnogno
  • groups_test.gnogno
  • z_readme_filetest.gnogno
  • groups_test.gnogno
    1package groups23import (4	"strconv"5	"strings"6	"testing"78

    Functions

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

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

    "gno.land/p/nt/uassert/v0"
    9 "gno.land/p/nt/urequire/v0"
    10)
    11
    12var (
    13 addr0 = address("addr0")
    14 addr1 = address("addr1")
    15 addr2 = address("addr2")
    16 addr3 = address("addr3")
    17 addr4 = address("addr4")
    18 addr5 = address("addr5")
    19 addr6 = address("addr6")
    20)
    21
    22// newTestGroup builds the fixture used across tests:
    23//
    24// base: addr0, addr1, addr3, addr5
    25// admins: addr2, addr3, addr5
    26// mods: addr1, addr4, addr5
    27//
    28// Overlap topology: addr0 base-only, addr2 admins-only, addr4 mods-only,
    29// addr1 base+mods, addr3 base+admins, addr5 base+both roles.
    30// Distinct addresses: addr0..addr5 (addr6 is never a member).
    31func newTestGroup(t *testing.T) *Group {
    32 t.Helper()
    33 g := NewGroup()
    34 g.Add(addr0)
    35 g.Add(addr1)
    36 g.Add(addr3)
    37 g.Add(addr5)
    38
    39 admins, err := g.AddRole("admins")
    40 urequire.NoError(t, err)
    41 admins.Members().Add(addr2)
    42 admins.Members().Add(addr3)
    43 admins.Members().Add(addr5)
    44
    45 mods, err := g.AddRole("mods")
    46 urequire.NoError(t, err)
    47 mods.Members().Add(addr1)
    48 mods.Members().Add(addr4)
    49 mods.Members().Add(addr5)
    50 return g
    51}
    52
    53func joinAddrs(addrs []address) string {
    54 strs := make([]string, 0, len(addrs))
    55 for _, a := range addrs {
    56 strs = append(strs, string(a))
    57 }
    58 return strings.Join(strs, ",")
    59}
    60
    61func TestBaseSet(t *testing.T) {
    62 g := NewGroup()
    63 uassert.Equal(t, 0, g.Size())
    64 uassert.False(t, g.Has(addr1))
    65
    66 uassert.True(t, g.Add(addr1))
    67 uassert.False(t, g.Add(addr1)) // duplicate
    68 uassert.True(t, g.Add(addr2))
    69 uassert.Equal(t, 2, g.Size())
    70 uassert.True(t, g.Has(addr1))
    71 uassert.False(t, g.Has(addr3))
    72
    73 uassert.True(t, g.Remove(addr1))
    74 uassert.False(t, g.Remove(addr1)) // already gone
    75 uassert.Equal(t, 1, g.Size())
    76 uassert.False(t, g.Has(addr1))
    77}
    78
    79func TestIterate(t *testing.T) {
    80 g := NewGroup()
    81 g.Add(addr2)
    82 g.Add(addr1)
    83 g.Add(addr3)
    84
    85 var got []address
    86 stopped := g.Iterate(0, 10, func(a address) bool {
    87 got = append(got, a)
    88 return false
    89 })
    90 uassert.False(t, stopped)
    91 uassert.Equal(t, "addr1,addr2,addr3", joinAddrs(got)) // sorted order
    92
    93 got = nil
    94 g.Iterate(1, 1, func(a address) bool {
    95 got = append(got, a)
    96 return false
    97 })
    98 uassert.Equal(t, "addr2", joinAddrs(got))
    99
    100 // Early stop halts iteration, not just the return value.
    101 visited := 0
    102 stopped = g.Iterate(0, 10, func(a address) bool {
    103 visited++
    104 return true
    105 })
    106 uassert.True(t, stopped)
    107 uassert.Equal(t, 1, visited)
    108
    109 // Roles are never consulted.
    110 gg := newTestGroup(t)
    111 got = nil
    112 gg.Iterate(0, 10, func(a address) bool {
    113 got = append(got, a)
    114 return false
    115 })
    116 uassert.Equal(t, "addr0,addr1,addr3,addr5", joinAddrs(got))
    117}
    118
    119func TestAddRole(t *testing.T) {
    120 g := NewGroup()
    121
    122 r, err := g.AddRole("admins")
    123 urequire.NoError(t, err)
    124 uassert.Equal(t, "admins", r.Name())
    125 uassert.Equal(t, 0, r.Members().Size())
    126 uassert.Equal(t, 1, g.RoleCount())
    127
    128 _, err = g.AddRole("admins")
    129 uassert.ErrorIs(t, err, ErrRoleExists)
    130
    131 _, err = g.AddRole("")
    132 uassert.ErrorIs(t, err, ErrEmptyName)
    133
    134 uassert.Equal(t, 1, g.RoleCount())
    135}
    136
    137func TestGetRoleHasRole(t *testing.T) {
    138 g := newTestGroup(t)
    139
    140 r, found := g.GetRole("admins")
    141 uassert.True(t, found)
    142 uassert.Equal(t, "admins", r.Name())
    143 uassert.True(t, r.Members().Has(addr2))
    144
    145 _, found = g.GetRole("nope")
    146 uassert.False(t, found)
    147
    148 uassert.True(t, g.HasRole("mods"))
    149 uassert.False(t, g.HasRole("nope"))
    150}
    151
    152func TestRemoveRole(t *testing.T) {
    153 g := newTestGroup(t)
    154
    155 uassert.True(t, g.RemoveRole("admins"))
    156 uassert.False(t, g.RemoveRole("admins")) // already gone
    157 uassert.Equal(t, 1, g.RoleCount())
    158 uassert.False(t, g.HasRole("admins"))
    159
    160 // Members of the removed role stay in the base set and other roles.
    161 uassert.True(t, g.Has(addr3)) // base
    162 uassert.True(t, g.HasAny(addr5)) // still in base + mods
    163 uassert.False(t, g.HasAny(addr2)) // was only in admins
    164}
    165
    166func TestIterateRoles(t *testing.T) {
    167 g := newTestGroup(t)
    168
    169 var names []string
    170 stopped := g.IterateRoles(0, 10, func(rr *ReadonlyRole) bool {
    171 names = append(names, rr.Name())
    172 return false
    173 })
    174 uassert.False(t, stopped)
    175 uassert.Equal(t, "admins,mods", strings.Join(names, ","))
    176
    177 // offset/count window.
    178 names = nil
    179 g.IterateRoles(1, 1, func(rr *ReadonlyRole) bool {
    180 names = append(names, rr.Name())
    181 return false
    182 })
    183 uassert.Equal(t, "mods", strings.Join(names, ","))
    184
    185 // Early stop halts iteration, not just the return value.
    186 names = nil
    187 stopped = g.IterateRoles(0, 10, func(rr *ReadonlyRole) bool {
    188 names = append(names, rr.Name())
    189 return true
    190 })
    191 uassert.True(t, stopped)
    192 uassert.Equal(t, "admins", strings.Join(names, ","))
    193
    194 // Mutation path: capture names during iteration, revisit via GetRole.
    195 names = nil
    196 g.IterateRoles(0, 10, func(rr *ReadonlyRole) bool {
    197 names = append(names, rr.Name())
    198 return false
    199 })
    200 for _, name := range names {
    201 r, found := g.GetRole(name)
    202 urequire.True(t, found)
    203 r.Members().Add(addr6)
    204 }
    205 uassert.Equal(t, "admins,mods", strings.Join(g.RolesContaining(addr6), ","))
    206}
    207
    208func TestRoleMeta(t *testing.T) {
    209 g := NewGroup()
    210 r, err := g.AddRole("admins")
    211 urequire.NoError(t, err)
    212
    213 uassert.Nil(t, r.Meta())
    214 r.SetMeta("quorum=2/3")
    215 uassert.Equal(t, "quorum=2/3", r.Meta())
    216 uassert.Equal(t, "quorum=2/3", r.Readonly().Meta())
    217 r.SetMeta(nil)
    218 uassert.Nil(t, r.Meta())
    219}
    220
    221func TestHasAny(t *testing.T) {
    222 g := newTestGroup(t)
    223
    224 uassert.True(t, g.HasAny(addr0)) // base only
    225 uassert.True(t, g.HasAny(addr1)) // base + mods
    226 uassert.True(t, g.HasAny(addr2)) // admins only
    227 uassert.True(t, g.HasAny(addr4)) // mods only
    228 uassert.True(t, g.HasAny(addr5)) // base + both roles
    229 uassert.False(t, g.HasAny(addr6)) // nowhere
    230
    231 // Base-only member, no roles registered at all.
    232 g2 := NewGroup()
    233 g2.Add(addr1)
    234 uassert.True(t, g2.HasAny(addr1))
    235 uassert.False(t, g2.HasAny(addr2))
    236}
    237
    238func TestTotalSize(t *testing.T) {
    239 uassert.Equal(t, 0, NewGroup().TotalSize())
    240
    241 g := newTestGroup(t)
    242 uassert.Equal(t, 4, g.Size()) // base only
    243 uassert.Equal(t, 6, g.TotalSize()) // addr0..addr5, deduplicated
    244}
    245
    246func TestIterateAll(t *testing.T) {
    247 g := newTestGroup(t)
    248
    249 collect := func(offset, count int) (addrs []address, stopped bool) {
    250 stopped = g.IterateAll(offset, count, func(a address) bool {
    251 addrs = append(addrs, a)
    252 return false
    253 })
    254 return addrs, stopped
    255 }
    256
    257 // Order: base first (sorted), then roles in name order, deduplicated.
    258 got, stopped := collect(0, 10)
    259 uassert.False(t, stopped)
    260 uassert.Equal(t, "addr0,addr1,addr3,addr5,addr2,addr4", joinAddrs(got))
    261
    262 // offset/count window over the deduplicated output.
    263 got, _ = collect(1, 2)
    264 uassert.Equal(t, "addr1,addr3", joinAddrs(got))
    265 got, _ = collect(3, 10)
    266 uassert.Equal(t, "addr5,addr2,addr4", joinAddrs(got))
    267 // Window straddling skipped duplicates: count applies to deduplicated
    268 // output, not to scanned items.
    269 got, _ = collect(4, 2)
    270 uassert.Equal(t, "addr2,addr4", joinAddrs(got))
    271
    272 // Degenerate parameters.
    273 got, stopped = collect(6, 10) // offset == total
    274 uassert.False(t, stopped)
    275 uassert.Equal(t, 0, len(got))
    276 got, _ = collect(0, 0)
    277 uassert.Equal(t, 0, len(got))
    278 got, _ = collect(-1, 2) // negative offset counts as zero
    279 uassert.Equal(t, "addr0,addr1", joinAddrs(got))
    280
    281 // Early stop halts iteration, not just the return value.
    282 visited := 0
    283 stopped = g.IterateAll(0, 10, func(a address) bool {
    284 visited++
    285 return true
    286 })
    287 uassert.True(t, stopped)
    288 uassert.Equal(t, 1, visited)
    289}
    290
    291// TestIterateAllWindows pins the pagination contract exhaustively: every
    292// (offset, count) window over the deduplicated walk must equal the
    293// corresponding slice of the full walk.
    294func TestIterateAllWindows(t *testing.T) {
    295 g := newTestGroup(t)
    296
    297 var full []address
    298 g.IterateAll(0, 100, func(a address) bool {
    299 full = append(full, a)
    300 return false
    301 })
    302 urequire.Equal(t, 6, len(full))
    303
    304 for offset := 0; offset <= len(full); offset++ {
    305 for count := 1; count <= len(full)+1; count++ {
    306 var page []address
    307 g.IterateAll(offset, count, func(a address) bool {
    308 page = append(page, a)
    309 return false
    310 })
    311 end := offset + count
    312 if end > len(full) {
    313 end = len(full)
    314 }
    315 label := "offset=" + strconv.Itoa(offset) + " count=" + strconv.Itoa(count)
    316 uassert.Equal(t, joinAddrs(full[offset:end]), joinAddrs(page), label)
    317 }
    318 }
    319}
    320
    321func TestRolesContaining(t *testing.T) {
    322 g := newTestGroup(t)
    323
    324 uassert.Equal(t, "admins,mods", strings.Join(g.RolesContaining(addr5), ","))
    325 uassert.Equal(t, "admins", strings.Join(g.RolesContaining(addr2), ","))
    326 uassert.Equal(t, "mods", strings.Join(g.RolesContaining(addr1), ",")) // base not consulted
    327 uassert.Equal(t, 0, len(g.RolesContaining(addr0))) // base-only member
    328 uassert.Equal(t, 0, len(g.RolesContaining(addr6)))
    329}
    330
    331func TestRemoveFromAll(t *testing.T) {
    332 g := newTestGroup(t)
    333
    334 uassert.True(t, g.RemoveFromAll(addr5)) // base + both roles
    335 uassert.False(t, g.HasAny(addr5))
    336
    337 uassert.True(t, g.RemoveFromAll(addr0)) // base-only: removed reflects the base hit alone
    338 uassert.False(t, g.HasAny(addr0))
    339
    340 uassert.True(t, g.RemoveFromAll(addr2)) // role-only
    341 uassert.False(t, g.HasAny(addr2))
    342
    343 uassert.False(t, g.RemoveFromAll(addr6)) // nowhere
    344
    345 uassert.Equal(t, 3, g.TotalSize()) // addr1, addr3, addr4 remain
    346 uassert.Equal(t, 2, g.RoleCount()) // roles themselves are kept
    347}
    348
    349// TestRemoveRoleStaleHandle pins the aliasing semantics: a *Role handle
    350// held across RemoveRole keeps working as a detached object, but its
    351// mutations no longer affect the group, and re-adding the name creates a
    352// fresh, independent role.
    353func TestRemoveRoleStaleHandle(t *testing.T) {
    354 g := newTestGroup(t)
    355 stale, found := g.GetRole("admins")
    356 urequire.True(t, found)
    357
    358 uassert.True(t, g.RemoveRole("admins"))
    359
    360 // The detached role still works as an orphan object...
    361 uassert.Equal(t, "admins", stale.Name())
    362 stale.Members().Add(addr6)
    363 uassert.True(t, stale.Members().Has(addr6))
    364
    365 // ...but no longer affects the group.
    366 uassert.False(t, g.HasAny(addr6))
    367 uassert.Equal(t, 0, len(g.RolesContaining(addr6)))
    368 uassert.False(t, g.HasAny(addr2)) // addr2 was admins-only
    369 uassert.Equal(t, 5, g.TotalSize()) // fixture minus admins-only addr2
    370
    371 // Re-adding the name yields a fresh empty role, not the stale one.
    372 fresh, err := g.AddRole("admins")
    373 urequire.NoError(t, err)
    374 uassert.Equal(t, 0, fresh.Members().Size())
    375 uassert.False(t, g.HasAny(addr6))
    376}
    377
    378func TestReadonlyGroup(t *testing.T) {
    379 g := newTestGroup(t)
    380 rg := g.Readonly()
    381
    382 // Base reads.
    383 uassert.True(t, rg.Has(addr0))
    384 uassert.False(t, rg.Has(addr2))
    385 uassert.Equal(t, 4, rg.Size())
    386 var got []address
    387 stopped := rg.Iterate(0, 10, func(a address) bool {
    388 got = append(got, a)
    389 return false
    390 })
    391 uassert.False(t, stopped)
    392 uassert.Equal(t, "addr0,addr1,addr3,addr5", joinAddrs(got))
    393
    394 // Role registry reads.
    395 uassert.True(t, rg.HasRole("admins"))
    396 uassert.False(t, rg.HasRole("nope"))
    397 uassert.Equal(t, 2, rg.RoleCount())
    398 var names []string
    399 stopped = rg.IterateRoles(0, 10, func(rr *ReadonlyRole) bool {
    400 names = append(names, rr.Name())
    401 return false
    402 })
    403 uassert.False(t, stopped)
    404 uassert.Equal(t, "admins,mods", strings.Join(names, ","))
    405
    406 rr, found := rg.GetRole("admins")
    407 uassert.True(t, found)
    408 uassert.Equal(t, "admins", rr.Name())
    409 uassert.True(t, rr.Members().Has(addr2))
    410 uassert.Equal(t, 3, rr.Members().Size())
    411 _, found = rg.GetRole("nope")
    412 uassert.False(t, found)
    413
    414 // Aggregated reads.
    415 uassert.True(t, rg.HasAny(addr4))
    416 uassert.False(t, rg.HasAny(addr6))
    417 uassert.Equal(t, 6, rg.TotalSize())
    418 got = nil
    419 stopped = rg.IterateAll(0, 10, func(a address) bool {
    420 got = append(got, a)
    421 return false
    422 })
    423 uassert.False(t, stopped)
    424 uassert.Equal(t, "addr0,addr1,addr3,addr5,addr2,addr4", joinAddrs(got))
    425 uassert.True(t, rg.IterateAll(0, 10, func(a address) bool { return true }))
    426 uassert.Equal(t, "admins,mods", strings.Join(rg.RolesContaining(addr5), ","))
    427}
    428
    429func TestReadonlyViewsAreLive(t *testing.T) {
    430 g := newTestGroup(t)
    431 rg := g.Readonly()
    432 admins, found := g.GetRole("admins")
    433 urequire.True(t, found)
    434 rr := admins.Readonly()
    435
    436 // Views are thin handles, not snapshots: mutations through the owning
    437 // side are immediately visible through previously created views.
    438 g.Add(addr6)
    439 uassert.Equal(t, 5, rg.Size())
    440 uassert.True(t, rg.Has(addr6))
    441
    442 admins.Members().Add(addr6)
    443 uassert.Equal(t, 4, rr.Members().Size())
    444 uassert.True(t, rr.Members().Has(addr6))
    445}
    446