Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

permissions_test.gno

19.86 Kb · 714 lines
  1package permissions
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/gnoland/boards/v0"
  7	"gno.land/p/nt/uassert/v0"
  8	"gno.land/p/nt/urequire/v0"
  9)
 10
 11// Test permission constants
 12const (
 13	testPermA boards.Permission = iota
 14	testPermB
 15	testPermC
 16)
 17
 18var _ boards.Permissions = (*Permissions)(nil)
 19
 20func TestBasicPermissionsWithPermission(cur realm, t *testing.T) {
 21	cases := []struct {
 22		name       string
 23		user       address
 24		permission boards.Permission
 25		args       boards.Args
 26		setup      func() *Permissions
 27		err        string
 28		called     bool
 29	}{
 30		{
 31			name:       "ok",
 32			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
 33			permission: testPermA,
 34			setup: func() *Permissions {
 35				perms := New()
 36				perms.AddRole("foo", testPermA)
 37				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")
 38				return perms
 39			},
 40			called: true,
 41		},
 42		{
 43			name:       "ok with arguments",
 44			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
 45			permission: testPermA,
 46			args:       boards.Args{"a", "b"},
 47			setup: func() *Permissions {
 48				perms := New()
 49				perms.AddRole("foo", testPermA)
 50				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")
 51				return perms
 52			},
 53			called: true,
 54		},
 55		{
 56			name:       "no permission",
 57			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
 58			permission: testPermA,
 59			setup: func() *Permissions {
 60				perms := New()
 61				perms.AddRole("foo", testPermA)
 62				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
 63				return perms
 64			},
 65			err: "unauthorized, user g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 doesn't have the required permission",
 66		},
 67		{
 68			name:       "is not a member",
 69			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
 70			permission: testPermA,
 71			setup: func() *Permissions {
 72				return New()
 73			},
 74			err: "unauthorized, user g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 doesn't have the required permission",
 75		},
 76	}
 77
 78	for _, tc := range cases {
 79		t.Run(tc.name, func(t *testing.T) {
 80			var called bool
 81
 82			perms := tc.setup()
 83			testCaseFn := func() {
 84				perms.WithPermission(tc.user, tc.permission, tc.args, func() {
 85					called = true
 86				})
 87			}
 88
 89			if tc.err != "" {
 90				urequire.PanicsWithMessage(t, cur, tc.err, testCaseFn, "expect panic with message")
 91				return
 92			} else {
 93				urequire.NotPanics(t, cur, testCaseFn, "expect no panic")
 94			}
 95
 96			urequire.Equal(t, tc.called, called, "expect callback to be called")
 97		})
 98	}
 99}
100
101func TestBasicPermissionsSetPublicPermissions(t *testing.T) {
102	user := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
103	perms := New()
104
105	// Add a new role with permissions
106	perms.AddRole("adminRole", testPermA, testPermB, testPermC)
107	urequire.False(t, perms.HasPermission(user, testPermA))
108	urequire.False(t, perms.HasPermission(user, testPermB))
109	urequire.False(t, perms.HasPermission(user, testPermC))
110
111	// Assign a couple of public permissions
112	perms.SetPublicPermissions(testPermA, testPermC)
113	urequire.True(t, perms.HasPermission(user, testPermA))
114	urequire.False(t, perms.HasPermission(user, testPermB))
115	urequire.True(t, perms.HasPermission(user, testPermC))
116
117	// Clear all public permissions
118	perms.SetPublicPermissions()
119	urequire.False(t, perms.HasPermission(user, testPermA))
120	urequire.False(t, perms.HasPermission(user, testPermB))
121	urequire.False(t, perms.HasPermission(user, testPermC))
122}
123
124func TestBasicPermissionsGetUserRoles(t *testing.T) {
125	cases := []struct {
126		name  string
127		user  address
128		roles []string
129		setup func() *Permissions
130	}{
131		{
132			name:  "single role",
133			user:  "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
134			roles: []string{"admin"},
135			setup: func() *Permissions {
136				perms := New()
137				perms.AddRole("admin", testPermA)
138				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")
139				return perms
140			},
141		},
142		{
143			name:  "multiple roles",
144			user:  "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
145			roles: []string{"admin", "bar", "foo"},
146			setup: func() *Permissions {
147				perms := New()
148				perms.AddRole("admin", testPermA)
149				perms.AddRole("foo", testPermA)
150				perms.AddRole("bar", testPermA)
151				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo", "bar")
152				return perms
153			},
154		},
155		{
156			name: "without roles",
157			user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
158			setup: func() *Permissions {
159				perms := New()
160				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
161				return perms
162			},
163		},
164		{
165			name: "not a user",
166			user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
167			setup: func() *Permissions {
168				return New()
169			},
170		},
171		{
172			name:  "multiple users",
173			user:  "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
174			roles: []string{"admin"},
175			setup: func() *Permissions {
176				perms := New()
177				perms.AddRole("admin", testPermA)
178				perms.AddRole("bar", testPermA)
179				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")
180				perms.SetUserRoles("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "admin")
181				perms.SetUserRoles("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc", "admin", "bar")
182				return perms
183			},
184		},
185	}
186
187	for _, tc := range cases {
188		t.Run(tc.name, func(t *testing.T) {
189			perms := tc.setup()
190			roles := perms.GetUserRoles(tc.user)
191
192			urequire.Equal(t, len(tc.roles), len(roles), "user role count")
193			for i, r := range roles {
194				uassert.Equal(t, tc.roles[i], string(r))
195			}
196		})
197	}
198}
199
200func TestBasicPermissionsHasRole(t *testing.T) {
201	cases := []struct {
202		name  string
203		user  address
204		role  boards.Role
205		setup func() *Permissions
206		want  bool
207	}{
208		{
209			name: "ok",
210			user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
211			role: "admin",
212			setup: func() *Permissions {
213				perms := New()
214				perms.AddRole("admin", testPermA)
215				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin")
216				return perms
217			},
218			want: true,
219		},
220		{
221			name: "ok with multiple roles",
222			user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
223			role: "foo",
224			setup: func() *Permissions {
225				perms := New()
226				perms.AddRole("admin", testPermA)
227				perms.AddRole("foo", testPermA)
228				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "admin", "foo")
229				return perms
230			},
231			want: true,
232		},
233		{
234			name: "user without roles",
235			user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
236			setup: func() *Permissions {
237				perms := New()
238				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
239				return perms
240			},
241		},
242		{
243			name: "has no role",
244			user: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
245			role: "bar",
246			setup: func() *Permissions {
247				perms := New()
248				perms.AddRole("foo", testPermA)
249				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")
250				return perms
251			},
252		},
253	}
254
255	for _, tc := range cases {
256		t.Run(tc.name, func(t *testing.T) {
257			perms := tc.setup()
258			got := perms.HasRole(tc.user, tc.role)
259			uassert.Equal(t, tc.want, got)
260		})
261	}
262}
263
264func TestBasicPermissionsHasPermission(t *testing.T) {
265	cases := []struct {
266		name       string
267		user       address
268		permission boards.Permission
269		setup      func() *Permissions
270		want       bool
271	}{
272		{
273			name:       "ok",
274			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
275			permission: testPermA,
276			setup: func() *Permissions {
277				perms := New()
278				perms.AddRole("foo", testPermA)
279				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")
280				return perms
281			},
282			want: true,
283		},
284		{
285			name:       "ok with multiple users",
286			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
287			permission: testPermA,
288			setup: func() *Permissions {
289				perms := New()
290				perms.AddRole("foo", testPermA)
291				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")
292				perms.SetUserRoles("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "foo")
293				return perms
294			},
295			want: true,
296		},
297		{
298			name:       "ok with multiple roles",
299			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
300			permission: testPermB,
301			setup: func() *Permissions {
302				perms := New()
303				perms.AddRole("foo", testPermA)
304				perms.AddRole("baz", testPermB)
305				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo", "baz")
306				return perms
307			},
308			want: true,
309		},
310		{
311			name:       "no permission",
312			user:       "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
313			permission: testPermB,
314			setup: func() *Permissions {
315				perms := New()
316				perms.AddRole("foo", testPermA)
317				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "foo")
318				return perms
319			},
320		},
321	}
322
323	for _, tc := range cases {
324		t.Run(tc.name, func(t *testing.T) {
325			perms := tc.setup()
326			got := perms.HasPermission(tc.user, tc.permission)
327			uassert.Equal(t, tc.want, got)
328		})
329	}
330}
331
332func TestBasicPermissionsSetUserRoles(cur realm, t *testing.T) {
333	cases := []struct {
334		name          string
335		user          address
336		expectedRoles []boards.Role
337		setup         func() *Permissions
338		err           string
339	}{
340		{
341			name:          "add user",
342			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
343			expectedRoles: []boards.Role{"a"},
344			setup: func() *Permissions {
345				perms := New()
346				perms.AddRole("a", testPermA)
347				return perms
348			},
349		},
350		{
351			name:          "add user with multiple roles",
352			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
353			expectedRoles: []boards.Role{"a", "b"},
354			setup: func() *Permissions {
355				perms := New()
356				perms.AddRole("a", testPermA)
357				perms.AddRole("b", testPermB)
358				return perms
359			},
360		},
361		{
362			name:          "add when other users exists",
363			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
364			expectedRoles: []boards.Role{"a"},
365			setup: func() *Permissions {
366				perms := New()
367				perms.AddRole("a", testPermA)
368				perms.SetUserRoles("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn", "a")
369				perms.SetUserRoles("g1w4ek2u3jta047h6lta047h6lta047h6l9huexc")
370				return perms
371			},
372		},
373		{
374			name:          "add user using single role",
375			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
376			expectedRoles: []boards.Role{"a"},
377			setup: func() *Permissions {
378				perms := New(UseSingleUserRole())
379				perms.AddRole("a", testPermA)
380				return perms
381			},
382		},
383		{
384			name:          "update user roles",
385			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
386			expectedRoles: []boards.Role{"a", "b"},
387			setup: func() *Permissions {
388				perms := New()
389				perms.AddRole("a", testPermA)
390				perms.AddRole("b", testPermB)
391				perms.AddRole("c", testPermB)
392				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "c")
393				return perms
394			},
395		},
396		{
397			name:          "update user roles using single role",
398			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
399			expectedRoles: []boards.Role{"b"},
400			setup: func() *Permissions {
401				perms := New(UseSingleUserRole())
402				perms.AddRole("a", testPermA)
403				perms.AddRole("b", testPermB)
404				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "a")
405				return perms
406			},
407		},
408		{
409			name:          "clear user roles",
410			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
411			expectedRoles: []boards.Role{},
412			setup: func() *Permissions {
413				perms := New()
414				perms.AddRole("a", testPermA)
415				perms.AddRole("b", testPermB)
416				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5", "a", "b")
417				return perms
418			},
419		},
420		{
421			name:          "set invalid role",
422			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
423			expectedRoles: []boards.Role{"a", "foo"},
424			setup: func() *Permissions {
425				perms := New()
426				perms.AddRole("a", testPermA)
427				return perms
428			},
429			err: "invalid role: foo",
430		},
431		{
432			name:          "use single role error",
433			user:          address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
434			expectedRoles: []boards.Role{"a", "b"},
435			setup: func() *Permissions {
436				perms := New(UseSingleUserRole())
437				perms.AddRole("a", testPermA)
438				perms.AddRole("b", testPermB)
439				return perms
440			},
441			err: "user can only have one role",
442		},
443	}
444
445	for _, tc := range cases {
446		t.Run(tc.name, func(t *testing.T) {
447			perms := tc.setup()
448
449			setUserRoles := func() {
450				perms.SetUserRoles(tc.user, tc.expectedRoles...)
451			}
452
453			if tc.err != "" {
454				urequire.PanicsWithMessage(t, cur, tc.err, setUserRoles, "expected an error")
455				return
456			} else {
457				urequire.NotPanics(t, cur, setUserRoles, "expected no error")
458			}
459
460			roles := perms.GetUserRoles(tc.user)
461			uassert.Equal(t, len(tc.expectedRoles), len(roles))
462			for i, r := range roles {
463				urequire.Equal(t, string(tc.expectedRoles[i]), string(r))
464			}
465		})
466	}
467}
468
469func TestBasicPermissionsAddRoleOverwrite(t *testing.T) {
470	user := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
471
472	perms := New()
473	perms.AddRole("foo", testPermA)
474	perms.SetUserRoles(user, "foo")
475
476	// Re-adding an existing role replaces its permissions and keeps members
477	perms.AddRole("foo", testPermB)
478	uassert.True(t, perms.HasRole(user, "foo"), "expect role members to be kept")
479	uassert.False(t, perms.HasPermission(user, testPermA), "expect old permissions to be revoked")
480	uassert.True(t, perms.HasPermission(user, testPermB), "expect new permissions to be granted")
481}
482
483func TestBasicPermissionsAddRoleEmptyName(cur realm, t *testing.T) {
484	urequire.PanicsWithMessage(t, cur, "role name is required", func() {
485		New().AddRole("  ", testPermA)
486	}, "expect whitespace only role names to be rejected")
487
488	urequire.PanicsWithMessage(t, cur, "permissions super role name is required", func() {
489		New(WithSuperRole("  "))
490	}, "expect whitespace only super role names to be rejected")
491
492	// The empty string is rejected before the superRole sentinel check, so
493	// the outcome doesn't depend on whether a super role is configured.
494	urequire.PanicsWithMessage(t, cur, "role name is required", func() {
495		New().AddRole("", testPermA)
496	}, "expect empty role name to be rejected without a super role")
497	urequire.PanicsWithMessage(t, cur, "role name is required", func() {
498		New(WithSuperRole("owner")).AddRole("", testPermA)
499	}, "expect empty role name to be rejected with a super role")
500}
501
502func TestBasicPermissionsSuperRole(t *testing.T) {
503	user := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
504	super := boards.Role("owner")
505
506	perms := New(WithSuperRole(super))
507	urequire.True(t, perms.RoleExists(super), "expect super role to exist")
508	urequire.False(t, perms.RoleExists("unknown"))
509
510	// Super role members get every permission without explicit mapping
511	perms.SetUserRoles(user, super)
512	urequire.True(t, perms.HasRole(user, super))
513	uassert.True(t, perms.HasUser(user))
514	uassert.True(t, perms.HasPermission(user, testPermA))
515	uassert.True(t, perms.HasPermission(user, testPermC))
516
517	// AddRole on the super role is a no-op: the super role branch grants
518	// every permission regardless of any meta a mapping would set
519	perms.AddRole(super, testPermB)
520	uassert.True(t, perms.HasPermission(user, testPermA), "expect super role to keep all permissions")
521}
522
523func TestBasicPermissionsHasUserUsersCount(t *testing.T) {
524	member := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
525	guest := address("g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn")
526
527	perms := New()
528	perms.AddRole("foo", testPermA)
529	urequire.Equal(t, 0, perms.UsersCount())
530	urequire.False(t, perms.HasUser(member))
531
532	// A user added only through a role counts as a user
533	perms.SetUserRoles(member, "foo")
534	urequire.True(t, perms.HasUser(member), "expect role holder to be a user")
535	urequire.Equal(t, 1, perms.UsersCount())
536
537	// A guest without roles counts as a user
538	perms.SetUserRoles(guest)
539	urequire.True(t, perms.HasUser(guest))
540	urequire.Equal(t, 2, perms.UsersCount())
541
542	// Clearing roles keeps the user and doesn't change the count
543	perms.SetUserRoles(member)
544	urequire.True(t, perms.HasUser(member), "expect user to remain after roles are cleared")
545	urequire.Equal(t, 2, perms.UsersCount())
546
547	// Re-assigning roles must not double count
548	perms.SetUserRoles(member, "foo")
549	urequire.Equal(t, 2, perms.UsersCount())
550}
551
552func TestBasicPermissionsSetUserRolesPanicKeepsState(cur realm, t *testing.T) {
553	user := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
554
555	perms := New()
556	perms.AddRole("a", testPermA)
557	perms.SetUserRoles(user, "a")
558
559	urequire.PanicsWithMessage(t, cur, "invalid role: nope", func() {
560		perms.SetUserRoles(user, "nope")
561	}, "expect invalid role to panic")
562
563	// The failed call must not have mutated state
564	uassert.True(t, perms.HasUser(user))
565	uassert.True(t, perms.HasRole(user, "a"))
566	uassert.Equal(t, 1, perms.UsersCount())
567}
568
569func TestBasicPermissionsRemoveUser(t *testing.T) {
570	cases := []struct {
571		name  string
572		user  address
573		setup func() *Permissions
574		want  bool
575	}{
576		{
577			name: "ok",
578			user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
579			setup: func() *Permissions {
580				perms := New()
581				perms.SetUserRoles("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
582				return perms
583			},
584			want: true,
585		},
586		{
587			name: "user not found",
588			user: address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5"),
589			setup: func() *Permissions {
590				return New()
591			},
592		},
593	}
594
595	for _, tc := range cases {
596		t.Run(tc.name, func(t *testing.T) {
597			perms := tc.setup()
598			got := perms.RemoveUser(tc.user)
599			uassert.Equal(t, tc.want, got)
600		})
601	}
602}
603
604func TestBasicPermissionsRemoveUserWithRoles(t *testing.T) {
605	user := address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5")
606
607	perms := New()
608	perms.AddRole("foo", testPermA)
609	perms.AddRole("bar", testPermB)
610	perms.SetUserRoles(user, "foo", "bar")
611
612	urequire.True(t, perms.RemoveUser(user), "expect role holder to be removed")
613
614	uassert.False(t, perms.HasUser(user), "expect removed user to not exist")
615	uassert.False(t, perms.HasRole(user, "foo"), "expect removed user to keep no roles")
616	uassert.False(t, perms.HasPermission(user, testPermA), "expect removed user to keep no permissions")
617	uassert.Equal(t, 0, len(perms.GetUserRoles(user)))
618	uassert.Equal(t, 0, perms.UsersCount())
619	uassert.False(t, perms.RemoveUser(user), "expect second removal to report user not found")
620}
621
622func TestBasicPermissionsIterateUsers(t *testing.T) {
623	// Users are listed sorted by address, including a roleless guest.
624	users := []boards.User{
625		{
626			Address: "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5",
627			Roles:   []boards.Role{"foo"},
628		},
629		{
630			Address: "g1us8428u2a5satrlxzagqqa5m6vmuze025anjlj",
631			Roles:   []boards.Role{"bar", "foo"},
632		},
633		{
634			Address: "g1vh7krmmzfua5xjmkatvmx09z37w34lsvd2mxa5",
635			Roles:   []boards.Role{"bar"},
636		},
637		{
638			Address: "g1w4ek2u33ta047h6lta047h6lta047h6ldvdwpn",
639		},
640	}
641
642	perms := New()
643	perms.AddRole("foo", testPermA)
644	perms.AddRole("bar", testPermB)
645	// Add users in reverse order to pin that iteration is address-sorted,
646	// not insertion-ordered.
647	for i := len(users) - 1; i >= 0; i-- {
648		perms.SetUserRoles(users[i].Address, users[i].Roles...)
649	}
650
651	cases := []struct {
652		name               string
653		start, count, want int
654	}{
655		{
656			name:  "exceed users count",
657			count: 50,
658			want:  4,
659		},
660		{
661			name:  "exact users count",
662			count: 4,
663			want:  4,
664		},
665		{
666			name:  "two users",
667			start: 1,
668			count: 2,
669			want:  2,
670		},
671		{
672			name:  "one user",
673			start: 1,
674			count: 1,
675			want:  1,
676		},
677		{
678			name:  "no iteration",
679			start: 50,
680			count: 1,
681		},
682	}
683
684	for _, tc := range cases {
685		t.Run(tc.name, func(t *testing.T) {
686			var visited int
687			stopped := perms.IterateUsers(tc.start, tc.count, func(u boards.User) bool {
688				i := tc.start + visited
689				urequire.True(t, i < len(users), "expect iterator to respect number of users")
690				uassert.Equal(t, users[i].Address, u.Address)
691
692				urequire.Equal(t, len(users[i].Roles), len(u.Roles), "expect number of roles to match")
693				for j := range u.Roles {
694					uassert.Equal(t, string(users[i].Roles[j]), string(u.Roles[j]))
695				}
696
697				visited++
698				return false
699			})
700
701			uassert.False(t, stopped, "expect full iteration not to report an early stop")
702			uassert.Equal(t, tc.want, visited, "expect iterator to visit the windowed users")
703		})
704	}
705
706	// Early stop is reported, and iteration actually halts.
707	var visited int
708	stopped := perms.IterateUsers(0, 10, func(boards.User) bool {
709		visited++
710		return true
711	})
712	uassert.True(t, stopped, "expect early stop to be reported")
713	uassert.Equal(t, 1, visited, "expect iteration to halt on early stop")
714}