Files
portal-mini-go/internal/service/admin/rbac/provider_test.go
T
2026-10-25 22:47:13 +08:00

204 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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)
}