Files
lijiaoqiao/supply-api/internal/iam/service/iam_service_real_test.go
Your Name f9fc984e5c test(iam): 使用TDD方法补充IAM模块测试覆盖
- 创建完整的IAM Service测试文件 (iam_service_real_test.go)
  - 测试真实 DefaultIAMService 而非 mock
  - 覆盖 CreateRole, GetRole, UpdateRole, DeleteRole, ListRoles
  - 覆盖 AssignRole, RevokeRole, GetUserRoles
  - 覆盖 CheckScope, GetUserScopes, IsExpired

- 创建完整的IAM Handler测试文件 (iam_handler_real_test.go)
  - 测试真实 IAMHandler 使用 httptest
  - 覆盖路由处理器方法 (handleRoles, handleRoleByCode等)
  - 覆盖 CreateRole, GetRole, ListRoles, UpdateRole, DeleteRole
  - 覆盖 AssignRole, RevokeRole, GetUserRoles, CheckScope, ListScopes
  - 覆盖辅助函数和中间件

- 修复原有代码bug
  - extractUserID: 修正索引从parts[3]到parts[4]
  - extractRoleCodeFromUserPath: 修正索引从parts[5]到parts[6]
  - 修复多余的空格导致的语法问题

测试覆盖率:
- IAM Handler: 0% -> 85.9%
- IAM Service: 0% -> 99.0%
2026-04-03 07:59:12 +08:00

1042 lines
23 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 service
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
// ==================== 构造函数测试 ====================
func TestNewDefaultIAMService(t *testing.T) {
// -arrange & act
svc := NewDefaultIAMService()
// assert
assert.NotNil(t, svc)
assert.NotNil(t, svc.roleStore)
assert.NotNil(t, svc.userRoleStore)
assert.NotNil(t, svc.roleScopeStore)
}
// ==================== CreateRole 测试 ====================
func TestIAMService_CreateRole_Success(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
req := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
Description: "平台开发者角色",
Scopes: []string{"platform:read", "router:invoke"},
}
// act
role, err := svc.CreateRole(ctx, 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.Equal(t, "平台开发者角色", role.Description)
assert.True(t, role.IsActive)
assert.Equal(t, 1, role.Version)
assert.False(t, role.CreatedAt.IsZero())
assert.False(t, role.UpdatedAt.IsZero())
}
func TestIAMService_CreateRole_WithParentCode(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// 先创建父角色
parentReq := &CreateRoleRequest{
Code: "admin",
Name: "管理员",
Type: "platform",
Level: 50,
Scopes: []string{"platform:admin"},
}
svc.CreateRole(ctx, parentReq)
// 创建子角色
req := &CreateRoleRequest{
Code: "operator",
Name: "运维人员",
Type: "platform",
Level: 30,
Scopes: []string{"platform:read", "platform:write"},
ParentCode: "admin",
}
// act
role, err := svc.CreateRole(ctx, req)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, "operator", role.Code)
}
func TestIAMService_CreateRole_DuplicateCode(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
req1 := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
Scopes: []string{"platform:read"},
}
svc.CreateRole(ctx, req1)
req2 := &CreateRoleRequest{
Code: "developer", // 重复的Code
Name: "另一个开发者",
Type: "platform",
Level: 20,
Scopes: []string{"platform:write"},
}
// act
role, err := svc.CreateRole(ctx, req2)
// assert
assert.Error(t, err)
assert.Nil(t, role)
assert.Equal(t, ErrDuplicateRoleCode, err)
}
func TestIAMService_CreateRole_InvalidType(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
req := &CreateRoleRequest{
Code: "unknown_role",
Name: "未知角色",
Type: "unknown_type", // 无效类型
Level: 10,
Scopes: []string{},
}
// act
role, err := svc.CreateRole(ctx, req)
// assert
assert.Error(t, err)
assert.Nil(t, role)
assert.Equal(t, ErrInvalidRequest, err)
}
func TestIAMService_CreateRole_AllValidTypes(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
validTypes := []string{"platform", "supply", "consumer"}
for i, roleType := range validTypes {
// arrange
req := &CreateRoleRequest{
Code: "role_" + roleType,
Name: "角色_" + roleType,
Type: roleType,
Level: 10 * (i + 1),
Scopes: []string{},
}
// act
role, err := svc.CreateRole(ctx, req)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, roleType, role.Type)
}
}
// ==================== GetRole 测试 ====================
func TestIAMService_GetRole_Success(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
Description: "只读角色",
}
svc.CreateRole(ctx, createReq)
// act
role, err := svc.GetRole(ctx, "viewer")
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, "viewer", role.Code)
assert.Equal(t, "查看者", role.Name)
assert.Equal(t, "platform", role.Type)
assert.Equal(t, 10, role.Level)
assert.Equal(t, "只读角色", role.Description)
assert.True(t, role.IsActive)
}
func TestIAMService_GetRole_NotFound(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
role, err := svc.GetRole(ctx, "nonexistent")
// assert
assert.Error(t, err)
assert.Nil(t, role)
assert.Equal(t, ErrRoleNotFound, err)
}
// ==================== UpdateRole 测试 ====================
func TestIAMService_UpdateRole_UpdateName(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
}
svc.CreateRole(ctx, createReq)
updateReq := &UpdateRoleRequest{
Code: "developer",
Name: "高级开发者",
}
// act
role, err := svc.UpdateRole(ctx, updateReq)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, "高级开发者", role.Name)
assert.Equal(t, 2, role.Version) // 版本应该递增
}
func TestIAMService_UpdateRole_UpdateDescription(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
}
svc.CreateRole(ctx, createReq)
updateReq := &UpdateRoleRequest{
Code: "developer",
Description: "负责平台功能开发",
}
// act
role, err := svc.UpdateRole(ctx, updateReq)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, "负责平台功能开发", role.Description)
}
func TestIAMService_UpdateRole_UpdateScopes(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
Scopes: []string{"platform:read"},
}
svc.CreateRole(ctx, createReq)
updateReq := &UpdateRoleRequest{
Code: "developer",
Scopes: []string{"platform:read", "platform:write", "router:invoke"},
}
// act
role, err := svc.UpdateRole(ctx, updateReq)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, []string{"platform:read", "platform:write", "router:invoke"}, svc.roleScopeStore["developer"])
}
func TestIAMService_UpdateRole_UpdateIsActive(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
}
svc.CreateRole(ctx, createReq)
isActive := false
updateReq := &UpdateRoleRequest{
Code: "developer",
IsActive: &isActive,
}
// act
role, err := svc.UpdateRole(ctx, updateReq)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.False(t, role.IsActive)
}
func TestIAMService_UpdateRole_NotFound(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
updateReq := &UpdateRoleRequest{
Code: "nonexistent",
Name: "不存在",
}
// act
role, err := svc.UpdateRole(ctx, updateReq)
// assert
assert.Error(t, err)
assert.Nil(t, role)
assert.Equal(t, ErrRoleNotFound, err)
}
func TestIAMService_UpdateRole_AllFields(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
}
svc.CreateRole(ctx, createReq)
isActive := false
updateReq := &UpdateRoleRequest{
Code: "developer",
Name: "高级开发者",
Description: "全面负责",
Scopes: []string{"platform:admin"},
IsActive: &isActive,
}
// act
role, err := svc.UpdateRole(ctx, updateReq)
// assert
assert.NoError(t, err)
assert.NotNil(t, role)
assert.Equal(t, "高级开发者", role.Name)
assert.Equal(t, "全面负责", role.Description)
assert.False(t, role.IsActive)
assert.Equal(t, 2, role.Version)
}
// ==================== DeleteRole 测试 ====================
func TestIAMService_DeleteRole_Success(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
}
svc.CreateRole(ctx, createReq)
// act
err := svc.DeleteRole(ctx, "developer")
// assert
assert.NoError(t, err)
// 验证软删除 - 角色应该还在但isActive为false
role, _ := svc.GetRole(ctx, "developer")
assert.NotNil(t, role)
assert.False(t, role.IsActive)
}
func TestIAMService_DeleteRole_NotFound(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
err := svc.DeleteRole(ctx, "nonexistent")
// assert
assert.Error(t, err)
assert.Equal(t, ErrRoleNotFound, err)
}
func TestIAMService_DeleteRole_UpdatesTimestamp(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
createReq := &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
}
svc.CreateRole(ctx, createReq)
time.Sleep(10 * time.Millisecond) // 确保时间戳有差异
// act
err := svc.DeleteRole(ctx, "developer")
// assert
assert.NoError(t, err)
role, _ := svc.GetRole(ctx, "developer")
assert.True(t, role.UpdatedAt.After(role.CreatedAt) || role.UpdatedAt.Equal(role.CreatedAt))
}
// ==================== ListRoles 测试 ====================
func TestIAMService_ListRoles_AllRoles(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// 创建多个角色
roles := []*CreateRoleRequest{
{Code: "viewer", Name: "查看者", Type: "platform", Level: 10},
{Code: "operator", Name: "运维", Type: "platform", Level: 30},
{Code: "supply_admin", Name: "供应管理员", Type: "supply", Level: 40},
{Code: "consumer_user", Name: "消费者用户", Type: "consumer", Level: 10},
}
for _, req := range roles {
svc.CreateRole(ctx, req)
}
// act
result, err := svc.ListRoles(ctx, "")
// assert
assert.NoError(t, err)
assert.Len(t, result, 4)
}
func TestIAMService_ListRoles_FilterByType(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// 创建不同类型的角色
svc.CreateRole(ctx, &CreateRoleRequest{Code: "viewer", Name: "查看者", Type: "platform", Level: 10})
svc.CreateRole(ctx, &CreateRoleRequest{Code: "operator", Name: "运维", Type: "platform", Level: 30})
svc.CreateRole(ctx, &CreateRoleRequest{Code: "supply_admin", Name: "供应管理员", Type: "supply", Level: 40})
// act
platformRoles, err := svc.ListRoles(ctx, "platform")
supplyRoles, err2 := svc.ListRoles(ctx, "supply")
consumerRoles, err3 := svc.ListRoles(ctx, "consumer")
// assert
assert.NoError(t, err)
assert.Len(t, platformRoles, 2)
assert.NoError(t, err2)
assert.Len(t, supplyRoles, 1)
assert.Equal(t, "supply", supplyRoles[0].Type)
assert.NoError(t, err3)
assert.Len(t, consumerRoles, 0)
}
func TestIAMService_ListRoles_Empty(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
roles, err := svc.ListRoles(ctx, "")
// assert
assert.NoError(t, err)
assert.Len(t, roles, 0)
}
// ==================== AssignRole 测试 ====================
func TestIAMService_AssignRole_Success(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// 创建角色
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
Scopes: []string{"platform:read"},
})
req := &AssignRoleRequest{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
}
// act
userRole, err := svc.AssignRole(ctx, req)
// assert
assert.NoError(t, err)
assert.NotNil(t, userRole)
assert.Equal(t, int64(100), userRole.UserID)
assert.Equal(t, "viewer", userRole.RoleCode)
assert.Equal(t, int64(1), userRole.TenantID)
assert.True(t, userRole.IsActive)
}
func TestIAMService_AssignRole_WithExpiresAt(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "temp_admin",
Name: "临时管理员",
Type: "platform",
Level: 50,
})
futureTime := time.Now().Add(24 * time.Hour)
req := &AssignRoleRequest{
UserID: 100,
RoleCode: "temp_admin",
TenantID: 1,
ExpiresAt: &futureTime,
}
// act
userRole, err := svc.AssignRole(ctx, req)
// assert
assert.NoError(t, err)
assert.NotNil(t, userRole)
assert.NotNil(t, userRole.ExpiresAt)
assert.False(t, userRole.IsExpired())
}
func TestIAMService_AssignRole_ExpiredRole(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "temp_admin",
Name: "临时管理员",
Type: "platform",
Level: 50,
})
pastTime := time.Now().Add(-1 * time.Hour)
req := &AssignRoleRequest{
UserID: 100,
RoleCode: "temp_admin",
TenantID: 1,
ExpiresAt: &pastTime,
}
// act
userRole, err := svc.AssignRole(ctx, req)
// assert
assert.NoError(t, err)
assert.NotNil(t, userRole)
assert.True(t, userRole.IsExpired())
}
func TestIAMService_AssignRole_RoleNotFound(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
req := &AssignRoleRequest{
UserID: 100,
RoleCode: "nonexistent",
TenantID: 1,
}
// act
userRole, err := svc.AssignRole(ctx, req)
// assert
assert.Error(t, err)
assert.Nil(t, userRole)
assert.Equal(t, ErrRoleNotFound, err)
}
func TestIAMService_AssignRole_DuplicateAssignment(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
})
req := &AssignRoleRequest{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
}
// 第一次分配
svc.AssignRole(ctx, req)
// act - 第二次分配同一个角色
userRole, err := svc.AssignRole(ctx, req)
// assert
assert.Error(t, err)
assert.Nil(t, userRole)
assert.Equal(t, ErrDuplicateAssignment, err)
}
func TestIAMService_AssignRole_SameRoleDifferentTenant(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
})
// act - 同一用户在同一角色上分配到不同租户
req1 := &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 1}
req2 := &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 2}
userRole1, err1 := svc.AssignRole(ctx, req1)
userRole2, err2 := svc.AssignRole(ctx, req2)
// assert
assert.NoError(t, err1)
assert.NotNil(t, userRole1)
assert.NoError(t, err2)
assert.NotNil(t, userRole2)
assert.Equal(t, int64(1), userRole1.TenantID)
assert.Equal(t, int64(2), userRole2.TenantID)
}
// ==================== RevokeRole 测试 ====================
func TestIAMService_RevokeRole_Success(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
})
svc.AssignRole(ctx, &AssignRoleRequest{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
})
// act
err := svc.RevokeRole(ctx, 100, "viewer", 1)
// assert
assert.NoError(t, err)
// 验证角色已被撤销
userRoles, _ := svc.GetUserRoles(ctx, 100)
assert.Len(t, userRoles, 0) // 因为IsActive=false不会返回
}
func TestIAMService_RevokeRole_NotFound(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
err := svc.RevokeRole(ctx, 100, "nonexistent", 1)
// assert
assert.Error(t, err)
assert.Equal(t, ErrRoleNotFound, err)
}
func TestIAMService_RevokeRole_Idempotent(t *testing.T) {
// RevokeRole是幂等操作重复撤销不会返回错误
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
})
svc.AssignRole(ctx, &AssignRoleRequest{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
})
// 先撤销一次
err1 := svc.RevokeRole(ctx, 100, "viewer", 1)
assert.NoError(t, err1)
// act - 再次撤销(幂等操作,不返回错误)
err2 := svc.RevokeRole(ctx, 100, "viewer", 1)
assert.NoError(t, err2) // 幂等操作,不会返回错误
}
// ==================== GetUserRoles 测试 ====================
func TestIAMService_GetUserRoles_MultipleRoles(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// 创建多个角色
svc.CreateRole(ctx, &CreateRoleRequest{Code: "viewer", Name: "查看者", Type: "platform", Level: 10})
svc.CreateRole(ctx, &CreateRoleRequest{Code: "developer", Name: "开发者", Type: "platform", Level: 20})
// 分配多个角色给同一用户
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 0})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "developer", TenantID: 0})
// act
userRoles, err := svc.GetUserRoles(ctx, 100)
// assert
assert.NoError(t, err)
assert.Len(t, userRoles, 2)
}
func TestIAMService_GetUserRoles_NoRoles(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
userRoles, err := svc.GetUserRoles(ctx, 999) // 不存在的用户
// assert
assert.NoError(t, err)
assert.Len(t, userRoles, 0)
}
func TestIAMService_GetUserRoles_ExcludesInactive(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{Code: "viewer", Name: "查看者", Type: "platform", Level: 10})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 1})
svc.RevokeRole(ctx, 100, "viewer", 1)
// act
userRoles, err := svc.GetUserRoles(ctx, 100)
// assert
assert.NoError(t, err)
assert.Len(t, userRoles, 0)
}
// ==================== CheckScope 测试 ====================
func TestIAMService_CheckScope_HasScope(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
Scopes: []string{"platform:read", "tenant:read"},
})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 0})
// act
hasScope, err := svc.CheckScope(ctx, 100, "platform:read")
// assert
assert.NoError(t, err)
assert.True(t, hasScope)
}
func TestIAMService_CheckScope_NoScope(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
Scopes: []string{"platform:read"},
})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 0})
// act
hasScope, err := svc.CheckScope(ctx, 100, "platform:write")
// assert
assert.NoError(t, err)
assert.False(t, hasScope)
}
func TestIAMService_CheckScope_WildcardScope(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "admin",
Name: "管理员",
Type: "platform",
Level: 50,
Scopes: []string{"*"}, // 通配符
})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "admin", TenantID: 0})
// act
hasScope, err := svc.CheckScope(ctx, 100, "any:scope")
// assert
assert.NoError(t, err)
assert.True(t, hasScope)
}
func TestIAMService_CheckScope_NoUser(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
hasScope, err := svc.CheckScope(ctx, 999, "platform:read")
// assert
assert.NoError(t, err)
assert.False(t, hasScope)
}
// ==================== GetUserScopes 测试 ====================
func TestIAMService_GetUserScopes_MultipleRoles(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
Scopes: []string{"platform:read", "tenant:read"},
})
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "developer",
Name: "开发者",
Type: "platform",
Level: 20,
Scopes: []string{"router:invoke", "router:model:list"},
})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 0})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "developer", TenantID: 0})
// act
scopes, err := svc.GetUserScopes(ctx, 100)
// assert
assert.NoError(t, err)
assert.Len(t, scopes, 4)
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")
}
func TestIAMService_GetUserScopes_Deduplication(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// 两个角色有相同的scope
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "viewer",
Name: "查看者",
Type: "platform",
Level: 10,
Scopes: []string{"platform:read"},
})
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "operator",
Name: "运维",
Type: "platform",
Level: 30,
Scopes: []string{"platform:read", "platform:write"},
})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "viewer", TenantID: 0})
svc.AssignRole(ctx, &AssignRoleRequest{UserID: 100, RoleCode: "operator", TenantID: 0})
// act
scopes, err := svc.GetUserScopes(ctx, 100)
// assert
assert.NoError(t, err)
assert.Len(t, scopes, 2) // 应该去重
assert.Contains(t, scopes, "platform:read")
assert.Contains(t, scopes, "platform:write")
}
func TestIAMService_GetUserScopes_NoRoles(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
// act
scopes, err := svc.GetUserScopes(ctx, 999)
// assert
assert.NoError(t, err)
assert.Len(t, scopes, 0)
}
func TestIAMService_GetUserScopes_ExpiredRoleExcluded(t *testing.T) {
// arrange
ctx := context.Background()
svc := NewDefaultIAMService()
svc.CreateRole(ctx, &CreateRoleRequest{
Code: "temp_admin",
Name: "临时管理员",
Type: "platform",
Level: 50,
Scopes: []string{"platform:admin"},
})
pastTime := time.Now().Add(-1 * time.Hour)
svc.AssignRole(ctx, &AssignRoleRequest{
UserID: 100,
RoleCode: "temp_admin",
TenantID: 1,
ExpiresAt: &pastTime,
})
// act
scopes, err := svc.GetUserScopes(ctx, 100)
// assert
assert.NoError(t, err)
assert.Len(t, scopes, 0) // 过期的角色应该被排除
}
// ==================== UserRole.IsExpired 测试 ====================
func TestUserRole_IsExpired_Nil(t *testing.T) {
// arrange
ur := &UserRole{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
ExpiresAt: nil,
}
// act & assert
assert.False(t, ur.IsExpired())
}
func TestUserRole_IsExpired_Future(t *testing.T) {
// arrange
futureTime := time.Now().Add(1 * time.Hour)
ur := &UserRole{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
ExpiresAt: &futureTime,
}
// act & assert
assert.False(t, ur.IsExpired())
}
func TestUserRole_IsExpired_Past(t *testing.T) {
// arrange
pastTime := time.Now().Add(-1 * time.Hour)
ur := &UserRole{
UserID: 100,
RoleCode: "viewer",
TenantID: 1,
ExpiresAt: &pastTime,
}
// act & assert
assert.True(t, ur.IsExpired())
}