package service import ( "context" "errors" "testing" apperrors "with_you/internal/errors" "with_you/internal/model" "github.com/google/uuid" "gorm.io/gorm" ) // fakeRoleRepo 实现 repository.RoleRepository 接口,用于测试。 type fakeRoleRepo struct { roles map[string]bool // userID -> role set(不支持多角色,测试足够) addErr error getRoleErr error rolesForUser []string getRoleByName *model.Role } func (f *fakeRoleRepo) GetUserRoles(ctx context.Context, userID string) ([]string, error) { return f.rolesForUser, nil } func (f *fakeRoleRepo) AddRoleForUser(ctx context.Context, userID, role string) error { if f.addErr != nil { return f.addErr } if f.roles == nil { f.roles = make(map[string]bool) } f.roles[userID+":"+role] = true return nil } func (f *fakeRoleRepo) DeleteRoleForUser(ctx context.Context, userID, role string) error { if f.roles == nil { return nil } delete(f.roles, userID+":"+role) return nil } func (f *fakeRoleRepo) DeleteAllRolesForUser(ctx context.Context, userID string) error { return nil } func (f *fakeRoleRepo) HasRole(ctx context.Context, userID, role string) (bool, error) { if f.roles == nil { return false, nil } return f.roles[userID+":"+role], nil } func (f *fakeRoleRepo) GetRole(ctx context.Context, name string) (*model.Role, error) { if f.getRoleErr != nil { return nil, f.getRoleErr } if f.getRoleByName != nil && f.getRoleByName.Name == name { return f.getRoleByName, nil } return &model.Role{Name: name}, nil } func (f *fakeRoleRepo) GetAllRoles(ctx context.Context) ([]model.Role, error) { return nil, nil } func (f *fakeRoleRepo) GetRoleUserCount(ctx context.Context, role string) (int64, error) { return 0, nil } // fakeCasbinForRole 测试 CasbinService 的最小实现,覆盖 AssignRole 所需方法。 type fakeCasbinForRole struct { roles map[string][]string // userID -> roles hasRoleMap map[string]bool // "userID:role" -> bool addErr error } func (f *fakeCasbinForRole) Enforce(ctx context.Context, sub, obj, act string) (bool, error) { return false, nil } func (f *fakeCasbinForRole) EnforceForUser(ctx context.Context, userID, obj, act string) (bool, error) { return false, nil } func (f *fakeCasbinForRole) AddRoleForUser(ctx context.Context, userID, role string) error { if f.addErr != nil { return f.addErr } if f.roles == nil { f.roles = make(map[string][]string) } f.roles[userID] = append(f.roles[userID], role) return nil } func (f *fakeCasbinForRole) DeleteRoleForUser(ctx context.Context, userID, role string) error { return nil } func (f *fakeCasbinForRole) GetRolesForUser(ctx context.Context, userID string) ([]string, error) { return f.roles[userID], nil } func (f *fakeCasbinForRole) HasRoleForUser(ctx context.Context, userID, role string) (bool, error) { if f.hasRoleMap == nil { return false, nil } return f.hasRoleMap[userID+":"+role], nil } func (f *fakeCasbinForRole) AddPolicy(ctx context.Context, role, resource, action string) error { return nil } func (f *fakeCasbinForRole) RemovePolicy(ctx context.Context, role, resource, action string) error { return nil } func (f *fakeCasbinForRole) LoadPolicy(ctx context.Context) error { return nil } func (f *fakeCasbinForRole) ClearCache(ctx context.Context) error { return nil } func (f *fakeCasbinForRole) GetPermissionsForRole(ctx context.Context, role string) ([][]string, error) { return nil, nil } func (f *fakeCasbinForRole) UpdatePermissionsForRole(ctx context.Context, role string, perms [][]string) error { return nil } func (f *fakeCasbinForRole) GetAllPermissions(ctx context.Context) [][]string { return nil } // TestAssignRole_NonSuperAdminAssigningSuperAdmin 非 super_admin 分配 super_admin → 拒绝。 func TestAssignRole_NonSuperAdminAssigningSuperAdmin(t *testing.T) { roleRepo := &fakeRoleRepo{ getRoleByName: &model.Role{Name: model.RoleSuperAdmin}, } casbinSvc := &fakeCasbinForRole{ roles: map[string][]string{ "operator": {model.RoleAdmin}, // operator 是普通 admin,不是 super_admin }, } svc := NewRoleService(roleRepo, casbinSvc) err := svc.AssignRole(context.Background(), "operator", "target", model.RoleSuperAdmin) if !errors.Is(err, apperrors.ErrCannotModifySuperAdmin) { t.Errorf("expected ErrCannotModifySuperAdmin, got %v", err) } } // TestAssignRole_SuperAdminAssigningSuperAdmin super_admin 分配 super_admin → 成功。 func TestAssignRole_SuperAdminAssigningSuperAdmin(t *testing.T) { roleRepo := &fakeRoleRepo{ getRoleByName: &model.Role{Name: model.RoleSuperAdmin}, } casbinSvc := &fakeCasbinForRole{ roles: map[string][]string{ "operator": {model.RoleSuperAdmin}, // operator 是 super_admin }, } svc := NewRoleService(roleRepo, casbinSvc) err := svc.AssignRole(context.Background(), "operator", "target", model.RoleSuperAdmin) if err != nil { t.Errorf("super_admin should be able to assign super_admin, got %v", err) } } // TestAssignRole_SelfModification 不能改自己的角色。 func TestAssignRole_SelfModification(t *testing.T) { roleRepo := &fakeRoleRepo{} casbinSvc := &fakeCasbinForRole{} svc := NewRoleService(roleRepo, casbinSvc) err := svc.AssignRole(context.Background(), "u1", "u1", model.RoleAdmin) if !errors.Is(err, apperrors.ErrCannotModifyOwnRole) { t.Errorf("expected ErrCannotModifyOwnRole, got %v", err) } } // TestRemoveRole_SuperAdminRole 不能移除 super_admin 角色。 func TestRemoveRole_SuperAdminRole(t *testing.T) { roleRepo := &fakeRoleRepo{ roles: map[string]bool{"target:" + model.RoleSuperAdmin: true}, } casbinSvc := &fakeCasbinForRole{ hasRoleMap: map[string]bool{"target:" + model.RoleSuperAdmin: true}, } svc := NewRoleService(roleRepo, casbinSvc) err := svc.RemoveRole(context.Background(), "operator", "target", model.RoleSuperAdmin) if !errors.Is(err, apperrors.ErrCannotModifySuperAdmin) { t.Errorf("expected ErrCannotModifySuperAdmin, got %v", err) } } // TestAssignRole_RoleNotFound 角色不存在 → ErrRoleNotFound。 func TestAssignRole_RoleNotFound(t *testing.T) { roleRepo := &fakeRoleRepo{ getRoleErr: gorm.ErrRecordNotFound, } casbinSvc := &fakeCasbinForRole{ roles: map[string][]string{ "operator": {model.RoleSuperAdmin}, }, } svc := NewRoleService(roleRepo, casbinSvc) err := svc.AssignRole(context.Background(), "operator", "target", model.RoleAdmin) if !errors.Is(err, apperrors.ErrRoleNotFound) { t.Errorf("expected ErrRoleNotFound, got %v", err) } } // 确保编译期 import 使用。 var _ = uuid.New