This commit is contained in:
2026-10-25 22:47:13 +08:00
parent 897d6c4483
commit b34fba352b
20 changed files with 2615 additions and 0 deletions
@@ -0,0 +1,203 @@
package rbac
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"portal-mini-go/internal/model/admin"
"portal-mini-go/internal/pkg/errors"
adminrepo "portal-mini-go/internal/repository/admin"
)
// ---- fakes ----
type fakeCache struct{ m map[uint64]memPair }
type memPair struct{ roles, perms []string }
func newFakeCache() *fakeCache { return &fakeCache{m: map[uint64]memPair{}} }
func (f *fakeCache) Get(_ context.Context, id uint64) ([]string, []string, bool) {
p, ok := f.m[id]
return p.roles, p.perms, ok
}
func (f *fakeCache) Set(_ context.Context, id uint64, roles, perms []string) {
f.m[id] = memPair{roles: roles, perms: perms}
}
func (f *fakeCache) Invalidate(_ context.Context, id uint64) { delete(f.m, id) }
func (f *fakeCache) InvalidateAll(_ context.Context) { f.m = map[uint64]memPair{} }
type fakeUserRepo struct {
roles map[uint64][]string
perms map[uint64][]string
count int64
created *admin.User
setRoles map[uint64][]uint64
}
func (r *fakeUserRepo) GetByUsername(context.Context, string) (*admin.User, error) {
return nil, errors.ErrNotFound
}
func (r *fakeUserRepo) GetByID(context.Context, uint64) (*admin.User, error) {
return nil, errors.ErrNotFound
}
func (r *fakeUserRepo) Create(_ context.Context, u *admin.User) error { r.created = u; return nil }
func (r *fakeUserRepo) Count(context.Context) (int64, error) { return r.count, nil }
func (r *fakeUserRepo) List(context.Context, adminrepo.UserListFilter) ([]admin.User, int64, error) {
return nil, 0, nil
}
func (r *fakeUserRepo) Update(context.Context, uint64, map[string]interface{}) error { return nil }
func (r *fakeUserRepo) Delete(context.Context, uint64) error { return nil }
func (r *fakeUserRepo) SetRoles(_ context.Context, id uint64, roleIDs []uint64) error {
if r.setRoles == nil {
r.setRoles = map[uint64][]uint64{}
}
r.setRoles[id] = roleIDs
return nil
}
func (r *fakeUserRepo) RoleCodesOf(_ context.Context, id uint64) ([]string, error) {
return r.roles[id], nil
}
func (r *fakeUserRepo) PermissionCodesOf(_ context.Context, id uint64) ([]string, error) {
return r.perms[id], nil
}
type fakeRoleRepo struct {
byCode map[string]*admin.Role
created *admin.Role
}
func newFakeRoleRepo() *fakeRoleRepo { return &fakeRoleRepo{byCode: map[string]*admin.Role{}} }
func (r *fakeRoleRepo) GetByID(context.Context, uint64) (*admin.Role, error) {
return nil, errors.ErrNotFound
}
func (r *fakeRoleRepo) GetByCode(_ context.Context, code string) (*admin.Role, error) {
if m, ok := r.byCode[code]; ok {
return m, nil
}
return nil, errors.ErrNotFound
}
func (r *fakeRoleRepo) Create(_ context.Context, m *admin.Role) error {
r.created = m
r.byCode[m.Code] = m
return nil
}
func (r *fakeRoleRepo) List(context.Context, adminrepo.RoleListFilter) ([]admin.Role, int64, error) {
return nil, 0, nil
}
func (r *fakeRoleRepo) Update(context.Context, uint64, map[string]interface{}) error { return nil }
func (r *fakeRoleRepo) Delete(context.Context, uint64) error { return nil }
func (r *fakeRoleRepo) SetPermissions(context.Context, uint64, []uint64) error { return nil }
func (r *fakeRoleRepo) PermissionCodesOf(context.Context, uint64) ([]string, error) { return nil, nil }
type fakePermRepo struct {
menus []admin.Permission
codes []string
}
func (r *fakePermRepo) GetByID(context.Context, uint64) (*admin.Permission, error) {
return nil, errors.ErrNotFound
}
func (r *fakePermRepo) Create(context.Context, *admin.Permission) error { return nil }
func (r *fakePermRepo) List(context.Context, adminrepo.PermListFilter) ([]admin.Permission, int64, error) {
return nil, 0, nil
}
func (r *fakePermRepo) Update(context.Context, uint64, map[string]interface{}) error { return nil }
func (r *fakePermRepo) Delete(context.Context, uint64) error { return nil }
func (r *fakePermRepo) AllMenuPermissions(context.Context) ([]admin.Permission, error) {
return r.menus, nil
}
func (r *fakePermRepo) AllCodes(context.Context) ([]string, error) { return r.codes, nil }
// ---- tests ----
func TestProvider_Load_Normal(t *testing.T) {
ur := &fakeUserRepo{
roles: map[uint64][]string{1: {"editor"}},
perms: map[uint64][]string{1: {"user:view"}},
}
pr := &fakePermRepo{codes: []string{"user:view", "user:manage", "mini:view"}}
p := NewProvider(ur, newFakeRoleRepo(), pr, newFakeCache())
roles, perms, err := p.Load(context.Background(), 1)
require.NoError(t, err)
require.Equal(t, []string{"editor"}, roles)
require.Equal(t, []string{"user:view"}, perms)
}
func TestProvider_Load_SuperAdmin(t *testing.T) {
ur := &fakeUserRepo{
roles: map[uint64][]string{1: {admin.SuperAdminRoleCode}},
perms: map[uint64][]string{1: {}},
}
pr := &fakePermRepo{codes: []string{"user:view", "user:manage"}}
p := NewProvider(ur, newFakeRoleRepo(), pr, newFakeCache())
_, perms, err := p.Load(context.Background(), 1)
require.NoError(t, err)
// 超级管理员短路:权限集为全部权限 code(忽略聚合结果)。
require.ElementsMatch(t, []string{"user:view", "user:manage"}, perms)
}
func TestProvider_MenuTree_Normal(t *testing.T) {
menus := []admin.Permission{
{ID: 1, Code: "dash", Type: admin.PermTypeMenu, ParentID: 0, Sort: 1},
{ID: 2, Code: "user", Type: admin.PermTypeMenu, ParentID: 1, Sort: 1},
{ID: 3, Code: "user:view", Type: admin.PermTypeMenu, ParentID: 2, Sort: 1},
{ID: 4, Code: "sys", Type: admin.PermTypeMenu, ParentID: 0, Sort: 2}, // 无权限且无后代
}
ur := &fakeUserRepo{
roles: map[uint64][]string{1: {"editor"}},
perms: map[uint64][]string{1: {"user:view"}},
}
pr := &fakePermRepo{menus: menus, codes: nil}
p := NewProvider(ur, newFakeRoleRepo(), pr, newFakeCache())
tree, err := p.MenuTree(context.Background(), 1)
require.NoError(t, err)
// 仅保留 dash -> user -> user:view;sys 被过滤。
require.Len(t, tree, 1)
require.Equal(t, uint64(1), tree[0].ID)
require.Len(t, tree[0].Children, 1)
require.Equal(t, uint64(2), tree[0].Children[0].ID)
require.Len(t, tree[0].Children[0].Children, 1)
require.Equal(t, uint64(3), tree[0].Children[0].Children[0].ID)
}
func TestProvider_MenuTree_SuperAdmin(t *testing.T) {
menus := []admin.Permission{
{ID: 1, Code: "dash", Type: admin.PermTypeMenu, ParentID: 0},
{ID: 4, Code: "sys", Type: admin.PermTypeMenu, ParentID: 0},
}
ur := &fakeUserRepo{roles: map[uint64][]string{1: {admin.SuperAdminRoleCode}}}
pr := &fakePermRepo{menus: menus}
p := NewProvider(ur, newFakeRoleRepo(), pr, newFakeCache())
tree, err := p.MenuTree(context.Background(), 1)
require.NoError(t, err)
require.Len(t, tree, 2) // 超级管理员返回完整树
}
func TestProvider_Seed(t *testing.T) {
ur := &fakeUserRepo{count: 0}
rr := newFakeRoleRepo()
pr := &fakePermRepo{codes: nil}
p := NewProvider(ur, rr, pr, newFakeCache())
require.NoError(t, p.Seed(context.Background(), "admin", "Admin@123456"))
require.NotNil(t, rr.created)
require.Equal(t, admin.SuperAdminRoleCode, rr.created.Code)
require.NotNil(t, ur.created)
require.Equal(t, "admin", ur.created.Username)
require.NotEmpty(t, ur.created.PasswordHash)
require.Equal(t, []uint64{rr.created.ID}, ur.setRoles[ur.created.ID])
// 已有管理员时不再创建。
ur2 := &fakeUserRepo{count: 3}
p2 := NewProvider(ur2, newFakeRoleRepo(), pr, newFakeCache())
require.NoError(t, p2.Seed(context.Background(), "admin", "x"))
require.Nil(t, ur2.created)
}