204 lines
7.3 KiB
Go
204 lines
7.3 KiB
Go
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)
|
||
}
|