433 lines
11 KiB
Go
433 lines
11 KiB
Go
|
|
package service
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"context"
|
|||
|
|
"testing"
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/stretchr/testify/assert"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// MockIAMService 模拟IAM服务(用于测试)
|
|||
|
|
type MockIAMService struct {
|
|||
|
|
roles map[string]*Role
|
|||
|
|
userRoles map[int64][]*UserRole
|
|||
|
|
roleScopes map[string][]string
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewMockIAMService() *MockIAMService {
|
|||
|
|
return &MockIAMService{
|
|||
|
|
roles: make(map[string]*Role),
|
|||
|
|
userRoles: make(map[int64][]*UserRole),
|
|||
|
|
roleScopes: make(map[string][]string),
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) CreateRole(ctx context.Context, req *CreateRoleRequest) (*Role, error) {
|
|||
|
|
if _, exists := m.roles[req.Code]; exists {
|
|||
|
|
return nil, ErrDuplicateRoleCode
|
|||
|
|
}
|
|||
|
|
role := &Role{
|
|||
|
|
Code: req.Code,
|
|||
|
|
Name: req.Name,
|
|||
|
|
Type: req.Type,
|
|||
|
|
Level: req.Level,
|
|||
|
|
IsActive: true,
|
|||
|
|
Version: 1,
|
|||
|
|
CreatedAt: time.Now(),
|
|||
|
|
UpdatedAt: time.Now(),
|
|||
|
|
}
|
|||
|
|
m.roles[req.Code] = role
|
|||
|
|
if len(req.Scopes) > 0 {
|
|||
|
|
m.roleScopes[req.Code] = req.Scopes
|
|||
|
|
}
|
|||
|
|
return role, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) GetRole(ctx context.Context, roleCode string) (*Role, error) {
|
|||
|
|
if role, exists := m.roles[roleCode]; exists {
|
|||
|
|
return role, nil
|
|||
|
|
}
|
|||
|
|
return nil, ErrRoleNotFound
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) UpdateRole(ctx context.Context, req *UpdateRoleRequest) (*Role, error) {
|
|||
|
|
role, exists := m.roles[req.Code]
|
|||
|
|
if !exists {
|
|||
|
|
return nil, ErrRoleNotFound
|
|||
|
|
}
|
|||
|
|
if req.Name != "" {
|
|||
|
|
role.Name = req.Name
|
|||
|
|
}
|
|||
|
|
if req.Description != "" {
|
|||
|
|
role.Description = req.Description
|
|||
|
|
}
|
|||
|
|
if req.Scopes != nil {
|
|||
|
|
m.roleScopes[req.Code] = req.Scopes
|
|||
|
|
}
|
|||
|
|
role.Version++
|
|||
|
|
role.UpdatedAt = time.Now()
|
|||
|
|
return role, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) DeleteRole(ctx context.Context, roleCode string) error {
|
|||
|
|
role, exists := m.roles[roleCode]
|
|||
|
|
if !exists {
|
|||
|
|
return ErrRoleNotFound
|
|||
|
|
}
|
|||
|
|
role.IsActive = false
|
|||
|
|
role.UpdatedAt = time.Now()
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) ListRoles(ctx context.Context, roleType string) ([]*Role, error) {
|
|||
|
|
var roles []*Role
|
|||
|
|
for _, role := range m.roles {
|
|||
|
|
if roleType == "" || role.Type == roleType {
|
|||
|
|
roles = append(roles, role)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return roles, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) AssignRole(ctx context.Context, req *AssignRoleRequest) (*modelUserRoleMapping, error) {
|
|||
|
|
for _, ur := range m.userRoles[req.UserID] {
|
|||
|
|
if ur.RoleCode == req.RoleCode && ur.TenantID == req.TenantID && ur.IsActive {
|
|||
|
|
return nil, ErrDuplicateAssignment
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
mapping := &modelUserRoleMapping{
|
|||
|
|
UserID: req.UserID,
|
|||
|
|
RoleCode: req.RoleCode,
|
|||
|
|
TenantID: req.TenantID,
|
|||
|
|
IsActive: true,
|
|||
|
|
}
|
|||
|
|
m.userRoles[req.UserID] = append(m.userRoles[req.UserID], &UserRole{
|
|||
|
|
UserID: req.UserID,
|
|||
|
|
RoleCode: req.RoleCode,
|
|||
|
|
TenantID: req.TenantID,
|
|||
|
|
IsActive: true,
|
|||
|
|
})
|
|||
|
|
return mapping, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) RevokeRole(ctx context.Context, userID int64, roleCode string, tenantID int64) error {
|
|||
|
|
for _, ur := range m.userRoles[userID] {
|
|||
|
|
if ur.RoleCode == roleCode && ur.TenantID == tenantID {
|
|||
|
|
ur.IsActive = false
|
|||
|
|
return nil
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return ErrRoleNotFound
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) GetUserRoles(ctx context.Context, userID int64) ([]*UserRole, error) {
|
|||
|
|
var userRoles []*UserRole
|
|||
|
|
for _, ur := range m.userRoles[userID] {
|
|||
|
|
if ur.IsActive {
|
|||
|
|
userRoles = append(userRoles, ur)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return userRoles, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) CheckScope(ctx context.Context, userID int64, requiredScope string) (bool, error) {
|
|||
|
|
scopes, err := m.GetUserScopes(ctx, userID)
|
|||
|
|
if err != nil {
|
|||
|
|
return false, err
|
|||
|
|
}
|
|||
|
|
for _, scope := range scopes {
|
|||
|
|
if scope == requiredScope || scope == "*" {
|
|||
|
|
return true, nil
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (m *MockIAMService) GetUserScopes(ctx context.Context, userID int64) ([]string, error) {
|
|||
|
|
var allScopes []string
|
|||
|
|
seen := make(map[string]bool)
|
|||
|
|
for _, ur := range m.userRoles[userID] {
|
|||
|
|
if ur.IsActive {
|
|||
|
|
if scopes, exists := m.roleScopes[ur.RoleCode]; exists {
|
|||
|
|
for _, scope := range scopes {
|
|||
|
|
if !seen[scope] {
|
|||
|
|
seen[scope] = true
|
|||
|
|
allScopes = append(allScopes, scope)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return allScopes, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// modelUserRoleMapping 简化的用户角色映射(用于测试)
|
|||
|
|
type modelUserRoleMapping struct {
|
|||
|
|
UserID int64
|
|||
|
|
RoleCode string
|
|||
|
|
TenantID int64
|
|||
|
|
IsActive bool
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_CreateRole_Success 测试创建角色成功
|
|||
|
|
func TestIAMService_CreateRole_Success(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
req := &CreateRoleRequest{
|
|||
|
|
Code: "developer",
|
|||
|
|
Name: "开发者",
|
|||
|
|
Type: "platform",
|
|||
|
|
Level: 20,
|
|||
|
|
Scopes: []string{"platform:read", "router:invoke"},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
role, err := mockService.CreateRole(context.Background(), req)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.NotNil(t, role)
|
|||
|
|
assert.Equal(t, "developer", role.Code)
|
|||
|
|
assert.Equal(t, "开发者", role.Name)
|
|||
|
|
assert.Equal(t, "platform", role.Type)
|
|||
|
|
assert.Equal(t, 20, role.Level)
|
|||
|
|
assert.True(t, role.IsActive)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_CreateRole_DuplicateName 测试创建重复角色
|
|||
|
|
func TestIAMService_CreateRole_DuplicateName(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["developer"] = &Role{Code: "developer", Name: "开发者", Type: "platform", Level: 20}
|
|||
|
|
|
|||
|
|
req := &CreateRoleRequest{
|
|||
|
|
Code: "developer",
|
|||
|
|
Name: "开发者",
|
|||
|
|
Type: "platform",
|
|||
|
|
Level: 20,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
role, err := mockService.CreateRole(context.Background(), req)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.Error(t, err)
|
|||
|
|
assert.Nil(t, role)
|
|||
|
|
assert.Equal(t, ErrDuplicateRoleCode, err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_UpdateRole_Success 测试更新角色成功
|
|||
|
|
func TestIAMService_UpdateRole_Success(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
existingRole := &Role{
|
|||
|
|
Code: "developer",
|
|||
|
|
Name: "开发者",
|
|||
|
|
Type: "platform",
|
|||
|
|
Level: 20,
|
|||
|
|
IsActive: true,
|
|||
|
|
Version: 1,
|
|||
|
|
}
|
|||
|
|
mockService.roles["developer"] = existingRole
|
|||
|
|
|
|||
|
|
req := &UpdateRoleRequest{
|
|||
|
|
Code: "developer",
|
|||
|
|
Name: "AI开发者",
|
|||
|
|
Description: "AI应用开发者",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
updatedRole, err := mockService.UpdateRole(context.Background(), req)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.NotNil(t, updatedRole)
|
|||
|
|
assert.Equal(t, "AI开发者", updatedRole.Name)
|
|||
|
|
assert.Equal(t, "AI应用开发者", updatedRole.Description)
|
|||
|
|
assert.Equal(t, 2, updatedRole.Version) // version 应该递增
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_UpdateRole_NotFound 测试更新不存在的角色
|
|||
|
|
func TestIAMService_UpdateRole_NotFound(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
|
|||
|
|
req := &UpdateRoleRequest{
|
|||
|
|
Code: "nonexistent",
|
|||
|
|
Name: "不存在",
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
role, err := mockService.UpdateRole(context.Background(), req)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.Error(t, err)
|
|||
|
|
assert.Nil(t, role)
|
|||
|
|
assert.Equal(t, ErrRoleNotFound, err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_DeleteRole_Success 测试删除角色成功
|
|||
|
|
func TestIAMService_DeleteRole_Success(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["developer"] = &Role{Code: "developer", Name: "开发者", IsActive: true}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
err := mockService.DeleteRole(context.Background(), "developer")
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.False(t, mockService.roles["developer"].IsActive) // 应该被停用而不是删除
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_ListRoles 测试列出角色
|
|||
|
|
func TestIAMService_ListRoles(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["viewer"] = &Role{Code: "viewer", Type: "platform", Level: 10}
|
|||
|
|
mockService.roles["operator"] = &Role{Code: "operator", Type: "platform", Level: 30}
|
|||
|
|
mockService.roles["supply_admin"] = &Role{Code: "supply_admin", Type: "supply", Level: 40}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
platformRoles, err := mockService.ListRoles(context.Background(), "platform")
|
|||
|
|
supplyRoles, err2 := mockService.ListRoles(context.Background(), "supply")
|
|||
|
|
allRoles, err3 := mockService.ListRoles(context.Background(), "")
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.Len(t, platformRoles, 2)
|
|||
|
|
|
|||
|
|
assert.NoError(t, err2)
|
|||
|
|
assert.Len(t, supplyRoles, 1)
|
|||
|
|
|
|||
|
|
assert.NoError(t, err3)
|
|||
|
|
assert.Len(t, allRoles, 3)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_AssignRole 测试分配角色
|
|||
|
|
func TestIAMService_AssignRole(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["viewer"] = &Role{Code: "viewer", Type: "platform", Level: 10}
|
|||
|
|
|
|||
|
|
req := &AssignRoleRequest{
|
|||
|
|
UserID: 100,
|
|||
|
|
RoleCode: "viewer",
|
|||
|
|
TenantID: 1,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
mapping, err := mockService.AssignRole(context.Background(), req)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.NotNil(t, mapping)
|
|||
|
|
assert.Equal(t, int64(100), mapping.UserID)
|
|||
|
|
assert.Equal(t, "viewer", mapping.RoleCode)
|
|||
|
|
assert.True(t, mapping.IsActive)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_AssignRole_Duplicate 测试重复分配角色
|
|||
|
|
func TestIAMService_AssignRole_Duplicate(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["viewer"] = &Role{Code: "viewer", Type: "platform", Level: 10}
|
|||
|
|
mockService.userRoles[100] = []*UserRole{
|
|||
|
|
{UserID: 100, RoleCode: "viewer", TenantID: 1, IsActive: true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
req := &AssignRoleRequest{
|
|||
|
|
UserID: 100,
|
|||
|
|
RoleCode: "viewer",
|
|||
|
|
TenantID: 1,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
mapping, err := mockService.AssignRole(context.Background(), req)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.Error(t, err)
|
|||
|
|
assert.Nil(t, mapping)
|
|||
|
|
assert.Equal(t, ErrDuplicateAssignment, err)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_RevokeRole 测试撤销角色
|
|||
|
|
func TestIAMService_RevokeRole(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.userRoles[100] = []*UserRole{
|
|||
|
|
{UserID: 100, RoleCode: "viewer", TenantID: 1, IsActive: true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
err := mockService.RevokeRole(context.Background(), 100, "viewer", 1)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.False(t, mockService.userRoles[100][0].IsActive)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_GetUserRoles 测试获取用户角色
|
|||
|
|
func TestIAMService_GetUserRoles(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.userRoles[100] = []*UserRole{
|
|||
|
|
{UserID: 100, RoleCode: "viewer", TenantID: 0, IsActive: true},
|
|||
|
|
{UserID: 100, RoleCode: "developer", TenantID: 1, IsActive: true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
roles, err := mockService.GetUserRoles(context.Background(), 100)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.Len(t, roles, 2)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_CheckScope 测试检查用户Scope
|
|||
|
|
func TestIAMService_CheckScope(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["viewer"] = &Role{Code: "viewer", Type: "platform", Level: 10}
|
|||
|
|
mockService.roleScopes["viewer"] = []string{"platform:read", "tenant:read"}
|
|||
|
|
mockService.userRoles[100] = []*UserRole{
|
|||
|
|
{UserID: 100, RoleCode: "viewer", TenantID: 0, IsActive: true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
hasScope, err := mockService.CheckScope(context.Background(), 100, "platform:read")
|
|||
|
|
noScope, err2 := mockService.CheckScope(context.Background(), 100, "platform:write")
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.True(t, hasScope)
|
|||
|
|
|
|||
|
|
assert.NoError(t, err2)
|
|||
|
|
assert.False(t, noScope)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TestIAMService_GetUserScopes 测试获取用户所有Scope
|
|||
|
|
func TestIAMService_GetUserScopes(t *testing.T) {
|
|||
|
|
// arrange
|
|||
|
|
mockService := NewMockIAMService()
|
|||
|
|
mockService.roles["viewer"] = &Role{Code: "viewer", Type: "platform", Level: 10}
|
|||
|
|
mockService.roles["developer"] = &Role{Code: "developer", Type: "platform", Level: 20}
|
|||
|
|
mockService.roleScopes["viewer"] = []string{"platform:read", "tenant:read"}
|
|||
|
|
mockService.roleScopes["developer"] = []string{"router:invoke", "router:model:list"}
|
|||
|
|
mockService.userRoles[100] = []*UserRole{
|
|||
|
|
{UserID: 100, RoleCode: "viewer", TenantID: 0, IsActive: true},
|
|||
|
|
{UserID: 100, RoleCode: "developer", TenantID: 0, IsActive: true},
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// act
|
|||
|
|
scopes, err := mockService.GetUserScopes(context.Background(), 100)
|
|||
|
|
|
|||
|
|
// assert
|
|||
|
|
assert.NoError(t, err)
|
|||
|
|
assert.Contains(t, scopes, "platform:read")
|
|||
|
|
assert.Contains(t, scopes, "tenant:read")
|
|||
|
|
assert.Contains(t, scopes, "router:invoke")
|
|||
|
|
assert.Contains(t, scopes, "router:model:list")
|
|||
|
|
}
|