feat(P1/P2): 完成TDD开发及P1/P2设计文档
## 设计文档 - multi_role_permission_design: 多角色权限设计 (CONDITIONAL GO) - audit_log_enhancement_design: 审计日志增强 (CONDITIONAL GO) - routing_strategy_template_design: 路由策略模板 (CONDITIONAL GO) - sso_saml_technical_research: SSO/SAML调研 (CONDITIONAL GO) - compliance_capability_package_design: 合规能力包设计 (CONDITIONAL GO) ## TDD开发成果 - IAM模块: supply-api/internal/iam/ (111个测试) - 审计日志模块: supply-api/internal/audit/ (40+测试) - 路由策略模块: gateway/internal/router/ (33+测试) - 合规能力包: gateway/internal/compliance/ + scripts/ci/compliance/ ## 规范文档 - parallel_agent_output_quality_standards: 并行Agent产出质量规范 - project_experience_summary: 项目经验总结 (v2) - 2026-04-02-p1-p2-tdd-execution-plan: TDD执行计划 ## 评审报告 - 5个CONDITIONAL GO设计文档评审报告 - fix_verification_report: 修复验证报告 - full_verification_report: 全面质量验证报告 - tdd_module_quality_verification: TDD模块质量验证 - tdd_execution_summary: TDD执行总结 依据: Superpowers执行框架 + TDD规范
This commit is contained in:
211
supply-api/internal/iam/model/role.go
Normal file
211
supply-api/internal/iam/model/role.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 角色类型常量
|
||||
const (
|
||||
RoleTypePlatform = "platform"
|
||||
RoleTypeSupply = "supply"
|
||||
RoleTypeConsumer = "consumer"
|
||||
)
|
||||
|
||||
// 角色层级常量(用于权限优先级判断)
|
||||
const (
|
||||
LevelSuperAdmin = 100
|
||||
LevelOrgAdmin = 50
|
||||
LevelSupplyAdmin = 40
|
||||
LevelOperator = 30
|
||||
LevelDeveloper = 20
|
||||
LevelFinops = 20
|
||||
LevelViewer = 10
|
||||
)
|
||||
|
||||
// 角色错误定义
|
||||
var (
|
||||
ErrInvalidRoleCode = errors.New("invalid role code: cannot be empty")
|
||||
ErrInvalidRoleType = errors.New("invalid role type: must be platform, supply, or consumer")
|
||||
ErrInvalidLevel = errors.New("invalid level: must be non-negative")
|
||||
)
|
||||
|
||||
// Role 角色模型
|
||||
// 对应数据库 iam_roles 表
|
||||
type Role struct {
|
||||
ID int64 // 主键ID
|
||||
Code string // 角色代码 (unique)
|
||||
Name string // 角色名称
|
||||
Type string // 角色类型: platform, supply, consumer
|
||||
ParentRoleID *int64 // 父角色ID(用于继承关系)
|
||||
Level int // 权限层级
|
||||
Description string // 描述
|
||||
IsActive bool // 是否激活
|
||||
|
||||
// 审计字段
|
||||
RequestID string // 请求追踪ID
|
||||
CreatedIP string // 创建者IP
|
||||
UpdatedIP string // 更新者IP
|
||||
Version int // 乐观锁版本号
|
||||
|
||||
// 时间戳
|
||||
CreatedAt *time.Time // 创建时间
|
||||
UpdatedAt *time.Time // 更新时间
|
||||
|
||||
// 关联的Scope列表(运行时填充,不存储在iam_roles表)
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
// NewRole 创建新角色(基础构造函数)
|
||||
func NewRole(code, name, roleType string, level int) *Role {
|
||||
now := time.Now()
|
||||
return &Role{
|
||||
Code: code,
|
||||
Name: name,
|
||||
Type: roleType,
|
||||
Level: level,
|
||||
IsActive: true,
|
||||
RequestID: generateRequestID(),
|
||||
Version: 1,
|
||||
CreatedAt: &now,
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRoleWithParent 创建带父角色的角色
|
||||
func NewRoleWithParent(code, name, roleType string, level int, parentRoleID int64) *Role {
|
||||
role := NewRole(code, name, roleType, level)
|
||||
role.ParentRoleID = &parentRoleID
|
||||
return role
|
||||
}
|
||||
|
||||
// NewRoleWithRequestID 创建带指定RequestID的角色
|
||||
func NewRoleWithRequestID(code, name, roleType string, level int, requestID string) *Role {
|
||||
role := NewRole(code, name, roleType, level)
|
||||
role.RequestID = requestID
|
||||
return role
|
||||
}
|
||||
|
||||
// NewRoleWithAudit 创建带审计信息的角色
|
||||
func NewRoleWithAudit(code, name, roleType string, level int, requestID, createdIP, updatedIP string) *Role {
|
||||
role := NewRole(code, name, roleType, level)
|
||||
role.RequestID = requestID
|
||||
role.CreatedIP = createdIP
|
||||
role.UpdatedIP = updatedIP
|
||||
return role
|
||||
}
|
||||
|
||||
// NewRoleWithValidation 创建角色并进行验证
|
||||
func NewRoleWithValidation(code, name, roleType string, level int) (*Role, error) {
|
||||
// 验证角色代码
|
||||
if code == "" {
|
||||
return nil, ErrInvalidRoleCode
|
||||
}
|
||||
|
||||
// 验证角色类型
|
||||
if roleType != RoleTypePlatform && roleType != RoleTypeSupply && roleType != RoleTypeConsumer {
|
||||
return nil, ErrInvalidRoleType
|
||||
}
|
||||
|
||||
// 验证层级
|
||||
if level < 0 {
|
||||
return nil, ErrInvalidLevel
|
||||
}
|
||||
|
||||
role := NewRole(code, name, roleType, level)
|
||||
return role, nil
|
||||
}
|
||||
|
||||
// Activate 激活角色
|
||||
func (r *Role) Activate() {
|
||||
r.IsActive = true
|
||||
r.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// Deactivate 停用角色
|
||||
func (r *Role) Deactivate() {
|
||||
r.IsActive = false
|
||||
r.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// IncrementVersion 递增版本号(用于乐观锁)
|
||||
func (r *Role) IncrementVersion() {
|
||||
r.Version++
|
||||
r.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// SetParentRole 设置父角色
|
||||
func (r *Role) SetParentRole(parentID int64) {
|
||||
r.ParentRoleID = &parentID
|
||||
}
|
||||
|
||||
// SetScopes 设置角色关联的Scope列表
|
||||
func (r *Role) SetScopes(scopes []string) {
|
||||
r.Scopes = scopes
|
||||
}
|
||||
|
||||
// AddScope 添加一个Scope
|
||||
func (r *Role) AddScope(scope string) {
|
||||
for _, s := range r.Scopes {
|
||||
if s == scope {
|
||||
return
|
||||
}
|
||||
}
|
||||
r.Scopes = append(r.Scopes, scope)
|
||||
}
|
||||
|
||||
// RemoveScope 移除一个Scope
|
||||
func (r *Role) RemoveScope(scope string) {
|
||||
newScopes := make([]string, 0, len(r.Scopes))
|
||||
for _, s := range r.Scopes {
|
||||
if s != scope {
|
||||
newScopes = append(newScopes, s)
|
||||
}
|
||||
}
|
||||
r.Scopes = newScopes
|
||||
}
|
||||
|
||||
// HasScope 检查角色是否拥有指定Scope
|
||||
func (r *Role) HasScope(scope string) bool {
|
||||
for _, s := range r.Scopes {
|
||||
if s == scope || s == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ToRoleScopeInfo 转换为RoleScopeInfo结构(用于API响应)
|
||||
func (r *Role) ToRoleScopeInfo() *RoleScopeInfo {
|
||||
return &RoleScopeInfo{
|
||||
RoleCode: r.Code,
|
||||
RoleName: r.Name,
|
||||
RoleType: r.Type,
|
||||
Level: r.Level,
|
||||
Scopes: r.Scopes,
|
||||
}
|
||||
}
|
||||
|
||||
// RoleScopeInfo 角色的Scope信息(用于API响应)
|
||||
type RoleScopeInfo struct {
|
||||
RoleCode string `json:"role_code"`
|
||||
RoleName string `json:"role_name"`
|
||||
RoleType string `json:"role_type"`
|
||||
Level int `json:"level"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
// generateRequestID 生成请求追踪ID
|
||||
func generateRequestID() string {
|
||||
b := make([]byte, 16)
|
||||
rand.Read(b)
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// nowPtr 返回当前时间的指针
|
||||
func nowPtr() *time.Time {
|
||||
t := time.Now()
|
||||
return &t
|
||||
}
|
||||
152
supply-api/internal/iam/model/role_scope.go
Normal file
152
supply-api/internal/iam/model/role_scope.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RoleScopeMapping 角色-Scope关联模型
|
||||
// 对应数据库 iam_role_scopes 表
|
||||
type RoleScopeMapping struct {
|
||||
ID int64 // 主键ID
|
||||
RoleID int64 // 角色ID (FK -> iam_roles.id)
|
||||
ScopeID int64 // ScopeID (FK -> iam_scopes.id)
|
||||
IsActive bool // 是否激活
|
||||
|
||||
// 审计字段
|
||||
RequestID string // 请求追踪ID
|
||||
CreatedIP string // 创建者IP
|
||||
Version int // 乐观锁版本号
|
||||
|
||||
// 时间戳
|
||||
CreatedAt *time.Time // 创建时间
|
||||
}
|
||||
|
||||
// NewRoleScopeMapping 创建新的角色-Scope映射
|
||||
func NewRoleScopeMapping(roleID, scopeID int64) *RoleScopeMapping {
|
||||
now := time.Now()
|
||||
return &RoleScopeMapping{
|
||||
RoleID: roleID,
|
||||
ScopeID: scopeID,
|
||||
IsActive: true,
|
||||
RequestID: generateRequestID(),
|
||||
Version: 1,
|
||||
CreatedAt: &now,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRoleScopeMappingWithAudit 创建带审计信息的角色-Scope映射
|
||||
func NewRoleScopeMappingWithAudit(roleID, scopeID int64, requestID, createdIP string) *RoleScopeMapping {
|
||||
now := time.Now()
|
||||
return &RoleScopeMapping{
|
||||
RoleID: roleID,
|
||||
ScopeID: scopeID,
|
||||
IsActive: true,
|
||||
RequestID: requestID,
|
||||
CreatedIP: createdIP,
|
||||
Version: 1,
|
||||
CreatedAt: &now,
|
||||
}
|
||||
}
|
||||
|
||||
// Revoke 撤销角色-Scope映射
|
||||
func (m *RoleScopeMapping) Revoke() {
|
||||
m.IsActive = false
|
||||
}
|
||||
|
||||
// Grant 授予角色-Scope映射
|
||||
func (m *RoleScopeMapping) Grant() {
|
||||
m.IsActive = true
|
||||
}
|
||||
|
||||
// IncrementVersion 递增版本号
|
||||
func (m *RoleScopeMapping) IncrementVersion() {
|
||||
m.Version++
|
||||
}
|
||||
|
||||
// GrantScopeList 批量授予Scope
|
||||
func GrantScopeList(roleID int64, scopeIDs []int64) []*RoleScopeMapping {
|
||||
mappings := make([]*RoleScopeMapping, 0, len(scopeIDs))
|
||||
for _, scopeID := range scopeIDs {
|
||||
mapping := NewRoleScopeMapping(roleID, scopeID)
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
return mappings
|
||||
}
|
||||
|
||||
// RevokeAll 撤销所有映射
|
||||
func RevokeAll(mappings []*RoleScopeMapping) {
|
||||
for _, mapping := range mappings {
|
||||
mapping.Revoke()
|
||||
}
|
||||
}
|
||||
|
||||
// GetActiveScopeIDs 从映射列表中获取活跃的Scope ID列表
|
||||
func GetActiveScopeIDs(mappings []*RoleScopeMapping) []int64 {
|
||||
activeIDs := make([]int64, 0, len(mappings))
|
||||
for _, mapping := range mappings {
|
||||
if mapping.IsActive {
|
||||
activeIDs = append(activeIDs, mapping.ScopeID)
|
||||
}
|
||||
}
|
||||
return activeIDs
|
||||
}
|
||||
|
||||
// GetInactiveScopeIDs 从映射列表中获取非活跃的Scope ID列表
|
||||
func GetInactiveScopeIDs(mappings []*RoleScopeMapping) []int64 {
|
||||
inactiveIDs := make([]int64, 0, len(mappings))
|
||||
for _, mapping := range mappings {
|
||||
if !mapping.IsActive {
|
||||
inactiveIDs = append(inactiveIDs, mapping.ScopeID)
|
||||
}
|
||||
}
|
||||
return inactiveIDs
|
||||
}
|
||||
|
||||
// FilterActiveMappings 过滤出活跃的映射
|
||||
func FilterActiveMappings(mappings []*RoleScopeMapping) []*RoleScopeMapping {
|
||||
active := make([]*RoleScopeMapping, 0, len(mappings))
|
||||
for _, mapping := range mappings {
|
||||
if mapping.IsActive {
|
||||
active = append(active, mapping)
|
||||
}
|
||||
}
|
||||
return active
|
||||
}
|
||||
|
||||
// FilterMappingsByRole 过滤出指定角色的映射
|
||||
func FilterMappingsByRole(mappings []*RoleScopeMapping, roleID int64) []*RoleScopeMapping {
|
||||
filtered := make([]*RoleScopeMapping, 0, len(mappings))
|
||||
for _, mapping := range mappings {
|
||||
if mapping.RoleID == roleID {
|
||||
filtered = append(filtered, mapping)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// FilterMappingsByScope 过滤出指定Scope的映射
|
||||
func FilterMappingsByScope(mappings []*RoleScopeMapping, scopeID int64) []*RoleScopeMapping {
|
||||
filtered := make([]*RoleScopeMapping, 0, len(mappings))
|
||||
for _, mapping := range mappings {
|
||||
if mapping.ScopeID == scopeID {
|
||||
filtered = append(filtered, mapping)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
// RoleScopeMappingInfo 角色-Scope映射信息(用于API响应)
|
||||
type RoleScopeMappingInfo struct {
|
||||
RoleID int64 `json:"role_id"`
|
||||
ScopeID int64 `json:"scope_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// ToInfo 转换为映射信息
|
||||
func (m *RoleScopeMapping) ToInfo() *RoleScopeMappingInfo {
|
||||
return &RoleScopeMappingInfo{
|
||||
RoleID: m.RoleID,
|
||||
ScopeID: m.ScopeID,
|
||||
IsActive: m.IsActive,
|
||||
}
|
||||
}
|
||||
157
supply-api/internal/iam/model/role_scope_test.go
Normal file
157
supply-api/internal/iam/model/role_scope_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestRoleScopeMapping_GrantScope 测试授予Scope
|
||||
func TestRoleScopeMapping_GrantScope(t *testing.T) {
|
||||
// arrange
|
||||
role := NewRole("operator", "运维人员", RoleTypePlatform, 30)
|
||||
role.ID = 1
|
||||
scope1 := NewScope("platform:read", "读取平台配置", ScopeTypePlatform)
|
||||
scope1.ID = 1
|
||||
scope2 := NewScope("platform:write", "修改平台配置", ScopeTypePlatform)
|
||||
scope2.ID = 2
|
||||
|
||||
// act
|
||||
roleScopeMapping := NewRoleScopeMapping(role.ID, scope1.ID)
|
||||
roleScopeMapping2 := NewRoleScopeMapping(role.ID, scope2.ID)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, role.ID, roleScopeMapping.RoleID)
|
||||
assert.Equal(t, scope1.ID, roleScopeMapping.ScopeID)
|
||||
assert.NotEmpty(t, roleScopeMapping.RequestID)
|
||||
assert.Equal(t, 1, roleScopeMapping.Version)
|
||||
|
||||
assert.Equal(t, role.ID, roleScopeMapping2.RoleID)
|
||||
assert.Equal(t, scope2.ID, roleScopeMapping2.ScopeID)
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_RevokeScope 测试撤销Scope
|
||||
func TestRoleScopeMapping_RevokeScope(t *testing.T) {
|
||||
// arrange
|
||||
role := NewRole("viewer", "查看者", RoleTypePlatform, 10)
|
||||
role.ID = 1
|
||||
scope := NewScope("platform:read", "读取平台配置", ScopeTypePlatform)
|
||||
scope.ID = 1
|
||||
|
||||
// act
|
||||
roleScopeMapping := NewRoleScopeMapping(role.ID, scope.ID)
|
||||
roleScopeMapping.Revoke()
|
||||
|
||||
// assert
|
||||
assert.False(t, roleScopeMapping.IsActive, "revoked mapping should be inactive")
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_WithAudit 测试带审计字段的映射
|
||||
func TestRoleScopeMapping_WithAudit(t *testing.T) {
|
||||
// arrange
|
||||
roleID := int64(1)
|
||||
scopeID := int64(2)
|
||||
requestID := "req-role-scope-123"
|
||||
createdIP := "192.168.1.100"
|
||||
|
||||
// act
|
||||
mapping := NewRoleScopeMappingWithAudit(roleID, scopeID, requestID, createdIP)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, roleID, mapping.RoleID)
|
||||
assert.Equal(t, scopeID, mapping.ScopeID)
|
||||
assert.Equal(t, requestID, mapping.RequestID)
|
||||
assert.Equal(t, createdIP, mapping.CreatedIP)
|
||||
assert.True(t, mapping.IsActive)
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_IncrementVersion 测试版本号递增
|
||||
func TestRoleScopeMapping_IncrementVersion(t *testing.T) {
|
||||
// arrange
|
||||
mapping := NewRoleScopeMapping(1, 1)
|
||||
originalVersion := mapping.Version
|
||||
|
||||
// act
|
||||
mapping.IncrementVersion()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, originalVersion+1, mapping.Version)
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_IsActive 测试活跃状态
|
||||
func TestRoleScopeMapping_IsActive(t *testing.T) {
|
||||
// arrange
|
||||
mapping := NewRoleScopeMapping(1, 1)
|
||||
|
||||
// assert - 默认应该激活
|
||||
assert.True(t, mapping.IsActive)
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_UniqueConstraint 测试唯一性(同一个角色和Scope组合)
|
||||
func TestRoleScopeMapping_UniqueConstraint(t *testing.T) {
|
||||
// arrange
|
||||
roleID := int64(1)
|
||||
scopeID := int64(1)
|
||||
|
||||
// act
|
||||
mapping1 := NewRoleScopeMapping(roleID, scopeID)
|
||||
mapping2 := NewRoleScopeMapping(roleID, scopeID)
|
||||
|
||||
// assert - 两个映射应该有相同的 RoleID 和 ScopeID(代表唯一约束)
|
||||
assert.Equal(t, mapping1.RoleID, mapping2.RoleID)
|
||||
assert.Equal(t, mapping1.ScopeID, mapping2.ScopeID)
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_GrantScopeList 测试批量授予Scope
|
||||
func TestRoleScopeMapping_GrantScopeList(t *testing.T) {
|
||||
// arrange
|
||||
roleID := int64(1)
|
||||
scopeIDs := []int64{1, 2, 3, 4, 5}
|
||||
|
||||
// act
|
||||
mappings := GrantScopeList(roleID, scopeIDs)
|
||||
|
||||
// assert
|
||||
assert.Len(t, mappings, len(scopeIDs))
|
||||
for i, scopeID := range scopeIDs {
|
||||
assert.Equal(t, roleID, mappings[i].RoleID)
|
||||
assert.Equal(t, scopeID, mappings[i].ScopeID)
|
||||
assert.True(t, mappings[i].IsActive)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_RevokeAll 测试撤销所有Scope(针对某个角色)
|
||||
func TestRoleScopeMapping_RevokeAll(t *testing.T) {
|
||||
// arrange
|
||||
roleID := int64(1)
|
||||
scopeIDs := []int64{1, 2, 3}
|
||||
mappings := GrantScopeList(roleID, scopeIDs)
|
||||
|
||||
// act
|
||||
RevokeAll(mappings)
|
||||
|
||||
// assert
|
||||
for _, mapping := range mappings {
|
||||
assert.False(t, mapping.IsActive, "all mappings should be revoked")
|
||||
}
|
||||
}
|
||||
|
||||
// TestRoleScopeMapping_GetActiveScopes 测试获取活跃的Scope列表
|
||||
func TestRoleScopeMapping_GetActiveScopes(t *testing.T) {
|
||||
// arrange
|
||||
roleID := int64(1)
|
||||
scopeIDs := []int64{1, 2, 3}
|
||||
mappings := GrantScopeList(roleID, scopeIDs)
|
||||
|
||||
// 撤销中间的Scope
|
||||
mappings[1].Revoke()
|
||||
|
||||
// act
|
||||
activeScopes := GetActiveScopeIDs(mappings)
|
||||
|
||||
// assert
|
||||
assert.Len(t, activeScopes, 2)
|
||||
assert.Contains(t, activeScopes, int64(1))
|
||||
assert.Contains(t, activeScopes, int64(3))
|
||||
assert.NotContains(t, activeScopes, int64(2))
|
||||
}
|
||||
244
supply-api/internal/iam/model/role_test.go
Normal file
244
supply-api/internal/iam/model/role_test.go
Normal file
@@ -0,0 +1,244 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestRoleModel_NewRole_ValidInput 测试创建角色 - 有效输入
|
||||
func TestRoleModel_NewRole_ValidInput(t *testing.T) {
|
||||
// arrange
|
||||
roleCode := "org_admin"
|
||||
roleName := "组织管理员"
|
||||
roleType := "platform"
|
||||
level := 50
|
||||
|
||||
// act
|
||||
role := NewRole(roleCode, roleName, roleType, level)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, roleCode, role.Code)
|
||||
assert.Equal(t, roleName, role.Name)
|
||||
assert.Equal(t, roleType, role.Type)
|
||||
assert.Equal(t, level, role.Level)
|
||||
assert.True(t, role.IsActive)
|
||||
assert.NotEmpty(t, role.RequestID)
|
||||
assert.Equal(t, 1, role.Version)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_DefaultFields 测试创建角色 - 验证默认字段
|
||||
func TestRoleModel_NewRole_DefaultFields(t *testing.T) {
|
||||
// arrange
|
||||
roleCode := "viewer"
|
||||
roleName := "查看者"
|
||||
roleType := "platform"
|
||||
level := 10
|
||||
|
||||
// act
|
||||
role := NewRole(roleCode, roleName, roleType, level)
|
||||
|
||||
// assert - 验证默认字段
|
||||
assert.Equal(t, 1, role.Version, "version should default to 1")
|
||||
assert.NotEmpty(t, role.RequestID, "request_id should be auto-generated")
|
||||
assert.True(t, role.IsActive, "is_active should default to true")
|
||||
assert.Nil(t, role.ParentRoleID, "parent_role_id should be nil for root roles")
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_WithParent 测试创建角色 - 带父角色
|
||||
func TestRoleModel_NewRole_WithParent(t *testing.T) {
|
||||
// arrange
|
||||
parentRole := NewRole("viewer", "查看者", "platform", 10)
|
||||
parentRole.ID = 1
|
||||
|
||||
// act
|
||||
childRole := NewRoleWithParent("developer", "开发者", "platform", 20, parentRole.ID)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, "developer", childRole.Code)
|
||||
assert.Equal(t, 20, childRole.Level)
|
||||
assert.NotNil(t, childRole.ParentRoleID)
|
||||
assert.Equal(t, parentRole.ID, *childRole.ParentRoleID)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_WithRequestID 测试创建角色 - 指定RequestID
|
||||
func TestRoleModel_NewRole_WithRequestID(t *testing.T) {
|
||||
// arrange
|
||||
requestID := "req-12345"
|
||||
|
||||
// act
|
||||
role := NewRoleWithRequestID("org_admin", "组织管理员", "platform", 50, requestID)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, requestID, role.RequestID)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_AuditFields 测试创建角色 - 审计字段
|
||||
func TestRoleModel_NewRole_AuditFields(t *testing.T) {
|
||||
// arrange
|
||||
createdIP := "192.168.1.1"
|
||||
updatedIP := "192.168.1.2"
|
||||
|
||||
// act
|
||||
role := NewRoleWithAudit("supply_admin", "供应方管理员", "supply", 40, "req-123", createdIP, updatedIP)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, createdIP, role.CreatedIP)
|
||||
assert.Equal(t, updatedIP, role.UpdatedIP)
|
||||
assert.Equal(t, 1, role.Version)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_Timestamps 测试创建角色 - 时间戳
|
||||
func TestRoleModel_NewRole_Timestamps(t *testing.T) {
|
||||
// arrange
|
||||
beforeCreate := time.Now()
|
||||
|
||||
// act
|
||||
role := NewRole("test_role", "测试角色", "platform", 10)
|
||||
_ = time.Now() // afterCreate not needed
|
||||
|
||||
// assert
|
||||
assert.NotNil(t, role.CreatedAt)
|
||||
assert.NotNil(t, role.UpdatedAt)
|
||||
assert.True(t, role.CreatedAt.After(beforeCreate) || role.CreatedAt.Equal(beforeCreate))
|
||||
assert.True(t, role.UpdatedAt.After(beforeCreate) || role.UpdatedAt.Equal(beforeCreate))
|
||||
}
|
||||
|
||||
// TestRoleModel_Activate 测试激活角色
|
||||
func TestRoleModel_Activate(t *testing.T) {
|
||||
// arrange
|
||||
role := NewRole("inactive_role", "非活跃角色", "platform", 10)
|
||||
role.IsActive = false
|
||||
|
||||
// act
|
||||
role.Activate()
|
||||
|
||||
// assert
|
||||
assert.True(t, role.IsActive)
|
||||
}
|
||||
|
||||
// TestRoleModel_Deactivate 测试停用角色
|
||||
func TestRoleModel_Deactivate(t *testing.T) {
|
||||
// arrange
|
||||
role := NewRole("active_role", "活跃角色", "platform", 10)
|
||||
|
||||
// act
|
||||
role.Deactivate()
|
||||
|
||||
// assert
|
||||
assert.False(t, role.IsActive)
|
||||
}
|
||||
|
||||
// TestRoleModel_IncrementVersion 测试版本号递增
|
||||
func TestRoleModel_IncrementVersion(t *testing.T) {
|
||||
// arrange
|
||||
role := NewRole("test_role", "测试角色", "platform", 10)
|
||||
originalVersion := role.Version
|
||||
|
||||
// act
|
||||
role.IncrementVersion()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, originalVersion+1, role.Version)
|
||||
}
|
||||
|
||||
// TestRoleModel_RoleType_Platform 测试平台角色类型
|
||||
func TestRoleModel_RoleType_Platform(t *testing.T) {
|
||||
// arrange & act
|
||||
role := NewRole("super_admin", "超级管理员", RoleTypePlatform, 100)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, RoleTypePlatform, role.Type)
|
||||
}
|
||||
|
||||
// TestRoleModel_RoleType_Supply 测试供应方角色类型
|
||||
func TestRoleModel_RoleType_Supply(t *testing.T) {
|
||||
// arrange & act
|
||||
role := NewRole("supply_admin", "供应方管理员", RoleTypeSupply, 40)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, RoleTypeSupply, role.Type)
|
||||
}
|
||||
|
||||
// TestRoleModel_RoleType_Consumer 测试需求方角色类型
|
||||
func TestRoleModel_RoleType_Consumer(t *testing.T) {
|
||||
// arrange & act
|
||||
role := NewRole("consumer_admin", "需求方管理员", RoleTypeConsumer, 40)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, RoleTypeConsumer, role.Type)
|
||||
}
|
||||
|
||||
// TestRoleModel_LevelHierarchy 测试角色层级关系
|
||||
func TestRoleModel_LevelHierarchy(t *testing.T) {
|
||||
// 测试设计文档中的层级关系
|
||||
// super_admin(100) > org_admin(50) > supply_admin(40) > operator(30) > developer/finops(20) > viewer(10)
|
||||
|
||||
// arrange
|
||||
superAdmin := NewRole("super_admin", "超级管理员", RoleTypePlatform, 100)
|
||||
orgAdmin := NewRole("org_admin", "组织管理员", RoleTypePlatform, 50)
|
||||
supplyAdmin := NewRole("supply_admin", "供应方管理员", RoleTypeSupply, 40)
|
||||
operator := NewRole("operator", "运维人员", RoleTypePlatform, 30)
|
||||
developer := NewRole("developer", "开发者", RoleTypePlatform, 20)
|
||||
viewer := NewRole("viewer", "查看者", RoleTypePlatform, 10)
|
||||
|
||||
// assert - 验证层级数值
|
||||
assert.Greater(t, superAdmin.Level, orgAdmin.Level)
|
||||
assert.Greater(t, orgAdmin.Level, supplyAdmin.Level)
|
||||
assert.Greater(t, supplyAdmin.Level, operator.Level)
|
||||
assert.Greater(t, operator.Level, developer.Level)
|
||||
assert.Greater(t, developer.Level, viewer.Level)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_EmptyCode 测试创建角色 - 空角色代码(应返回错误)
|
||||
func TestRoleModel_NewRole_EmptyCode(t *testing.T) {
|
||||
// arrange & act
|
||||
role, err := NewRoleWithValidation("", "测试角色", "platform", 10)
|
||||
|
||||
// assert
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, role)
|
||||
assert.Equal(t, ErrInvalidRoleCode, err)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_InvalidRoleType 测试创建角色 - 无效角色类型
|
||||
func TestRoleModel_NewRole_InvalidRoleType(t *testing.T) {
|
||||
// arrange & act
|
||||
role, err := NewRoleWithValidation("test_role", "测试角色", "invalid_type", 10)
|
||||
|
||||
// assert
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, role)
|
||||
assert.Equal(t, ErrInvalidRoleType, err)
|
||||
}
|
||||
|
||||
// TestRoleModel_NewRole_NegativeLevel 测试创建角色 - 负数层级
|
||||
func TestRoleModel_NewRole_NegativeLevel(t *testing.T) {
|
||||
// arrange & act
|
||||
role, err := NewRoleWithValidation("test_role", "测试角色", "platform", -1)
|
||||
|
||||
// assert
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, role)
|
||||
assert.Equal(t, ErrInvalidLevel, err)
|
||||
}
|
||||
|
||||
// TestRoleModel_ToRoleScopeInfo 测试角色转换为RoleScopeInfo
|
||||
func TestRoleModel_ToRoleScopeInfo(t *testing.T) {
|
||||
// arrange
|
||||
role := NewRole("org_admin", "组织管理员", RoleTypePlatform, 50)
|
||||
role.ID = 1
|
||||
role.Scopes = []string{"platform:read", "platform:write"}
|
||||
|
||||
// act
|
||||
roleScopeInfo := role.ToRoleScopeInfo()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, "org_admin", roleScopeInfo.RoleCode)
|
||||
assert.Equal(t, "组织管理员", roleScopeInfo.RoleName)
|
||||
assert.Equal(t, 50, roleScopeInfo.Level)
|
||||
assert.Len(t, roleScopeInfo.Scopes, 2)
|
||||
assert.Contains(t, roleScopeInfo.Scopes, "platform:read")
|
||||
assert.Contains(t, roleScopeInfo.Scopes, "platform:write")
|
||||
}
|
||||
225
supply-api/internal/iam/model/scope.go
Normal file
225
supply-api/internal/iam/model/scope.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Scope类型常量
|
||||
const (
|
||||
ScopeTypePlatform = "platform"
|
||||
ScopeTypeSupply = "supply"
|
||||
ScopeTypeConsumer = "consumer"
|
||||
ScopeTypeRouter = "router"
|
||||
ScopeTypeBilling = "billing"
|
||||
)
|
||||
|
||||
// Scope错误定义
|
||||
var (
|
||||
ErrInvalidScopeCode = errors.New("invalid scope code: cannot be empty")
|
||||
ErrInvalidScopeType = errors.New("invalid scope type: must be platform, supply, consumer, router, or billing")
|
||||
)
|
||||
|
||||
// Scope Scope模型
|
||||
// 对应数据库 iam_scopes 表
|
||||
type Scope struct {
|
||||
ID int64 // 主键ID
|
||||
Code string // Scope代码 (unique): platform:read, supply:account:write
|
||||
Name string // Scope名称
|
||||
Type string // Scope类型: platform, supply, consumer, router, billing
|
||||
Description string // 描述
|
||||
IsActive bool // 是否激活
|
||||
|
||||
// 审计字段
|
||||
RequestID string // 请求追踪ID
|
||||
CreatedIP string // 创建者IP
|
||||
UpdatedIP string // 更新者IP
|
||||
Version int // 乐观锁版本号
|
||||
|
||||
// 时间戳
|
||||
CreatedAt *time.Time // 创建时间
|
||||
UpdatedAt *time.Time // 更新时间
|
||||
}
|
||||
|
||||
// NewScope 创建新Scope(基础构造函数)
|
||||
func NewScope(code, name, scopeType string) *Scope {
|
||||
now := time.Now()
|
||||
return &Scope{
|
||||
Code: code,
|
||||
Name: name,
|
||||
Type: scopeType,
|
||||
IsActive: true,
|
||||
RequestID: generateRequestID(),
|
||||
Version: 1,
|
||||
CreatedAt: &now,
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
}
|
||||
|
||||
// NewScopeWithRequestID 创建带指定RequestID的Scope
|
||||
func NewScopeWithRequestID(code, name, scopeType string, requestID string) *Scope {
|
||||
scope := NewScope(code, name, scopeType)
|
||||
scope.RequestID = requestID
|
||||
return scope
|
||||
}
|
||||
|
||||
// NewScopeWithAudit 创建带审计信息的Scope
|
||||
func NewScopeWithAudit(code, name, scopeType string, requestID, createdIP, updatedIP string) *Scope {
|
||||
scope := NewScope(code, name, scopeType)
|
||||
scope.RequestID = requestID
|
||||
scope.CreatedIP = createdIP
|
||||
scope.UpdatedIP = updatedIP
|
||||
return scope
|
||||
}
|
||||
|
||||
// NewScopeWithValidation 创建Scope并进行验证
|
||||
func NewScopeWithValidation(code, name, scopeType string) (*Scope, error) {
|
||||
// 验证Scope代码
|
||||
if code == "" {
|
||||
return nil, ErrInvalidScopeCode
|
||||
}
|
||||
|
||||
// 验证Scope类型
|
||||
if !IsValidScopeType(scopeType) {
|
||||
return nil, ErrInvalidScopeType
|
||||
}
|
||||
|
||||
scope := NewScope(code, name, scopeType)
|
||||
return scope, nil
|
||||
}
|
||||
|
||||
// Activate 激活Scope
|
||||
func (s *Scope) Activate() {
|
||||
s.IsActive = true
|
||||
s.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// Deactivate 停用Scope
|
||||
func (s *Scope) Deactivate() {
|
||||
s.IsActive = false
|
||||
s.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// IncrementVersion 递增版本号(用于乐观锁)
|
||||
func (s *Scope) IncrementVersion() {
|
||||
s.Version++
|
||||
s.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// IsWildcard 检查是否为通配符Scope
|
||||
func (s *Scope) IsWildcard() bool {
|
||||
return s.Code == "*"
|
||||
}
|
||||
|
||||
// ToScopeInfo 转换为ScopeInfo结构(用于API响应)
|
||||
func (s *Scope) ToScopeInfo() *ScopeInfo {
|
||||
return &ScopeInfo{
|
||||
ScopeCode: s.Code,
|
||||
ScopeName: s.Name,
|
||||
ScopeType: s.Type,
|
||||
IsActive: s.IsActive,
|
||||
}
|
||||
}
|
||||
|
||||
// ScopeInfo Scope信息(用于API响应)
|
||||
type ScopeInfo struct {
|
||||
ScopeCode string `json:"scope_code"`
|
||||
ScopeName string `json:"scope_name"`
|
||||
ScopeType string `json:"scope_type"`
|
||||
IsActive bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// IsValidScopeType 验证Scope类型是否有效
|
||||
func IsValidScopeType(scopeType string) bool {
|
||||
switch scopeType {
|
||||
case ScopeTypePlatform, ScopeTypeSupply, ScopeTypeConsumer, ScopeTypeRouter, ScopeTypeBilling:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// GetScopeTypeFromCode 从Scope Code推断Scope类型
|
||||
// 例如: platform:read -> platform, supply:account:write -> supply, consumer:apikey:create -> consumer
|
||||
func GetScopeTypeFromCode(scopeCode string) string {
|
||||
parts := strings.SplitN(scopeCode, ":", 2)
|
||||
if len(parts) < 1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
prefix := parts[0]
|
||||
switch prefix {
|
||||
case "platform", "tenant", "billing":
|
||||
return ScopeTypePlatform
|
||||
case "supply":
|
||||
return ScopeTypeSupply
|
||||
case "consumer":
|
||||
return ScopeTypeConsumer
|
||||
case "router":
|
||||
return ScopeTypeRouter
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// PredefinedScopes 预定义的Scope列表
|
||||
var PredefinedScopes = []*Scope{
|
||||
// Platform Scopes
|
||||
{Code: "platform:read", Name: "读取平台配置", Type: ScopeTypePlatform},
|
||||
{Code: "platform:write", Name: "修改平台配置", Type: ScopeTypePlatform},
|
||||
{Code: "platform:admin", Name: "平台级管理", Type: ScopeTypePlatform},
|
||||
{Code: "platform:audit:read", Name: "读取审计日志", Type: ScopeTypePlatform},
|
||||
{Code: "platform:audit:export", Name: "导出审计日志", Type: ScopeTypePlatform},
|
||||
|
||||
// Tenant Scopes (属于platform类型)
|
||||
{Code: "tenant:read", Name: "读取租户信息", Type: ScopeTypePlatform},
|
||||
{Code: "tenant:write", Name: "修改租户配置", Type: ScopeTypePlatform},
|
||||
{Code: "tenant:member:manage", Name: "管理租户成员", Type: ScopeTypePlatform},
|
||||
{Code: "tenant:billing:write", Name: "修改账单设置", Type: ScopeTypePlatform},
|
||||
|
||||
// Supply Scopes
|
||||
{Code: "supply:account:read", Name: "读取供应账号", Type: ScopeTypeSupply},
|
||||
{Code: "supply:account:write", Name: "管理供应账号", Type: ScopeTypeSupply},
|
||||
{Code: "supply:package:read", Name: "读取套餐信息", Type: ScopeTypeSupply},
|
||||
{Code: "supply:package:write", Name: "管理套餐", Type: ScopeTypeSupply},
|
||||
{Code: "supply:package:publish", Name: "发布套餐", Type: ScopeTypeSupply},
|
||||
{Code: "supply:package:offline", Name: "下架套餐", Type: ScopeTypeSupply},
|
||||
{Code: "supply:settlement:withdraw", Name: "提现", Type: ScopeTypeSupply},
|
||||
{Code: "supply:credential:manage", Name: "管理凭证", Type: ScopeTypeSupply},
|
||||
|
||||
// Consumer Scopes
|
||||
{Code: "consumer:account:read", Name: "读取账户信息", Type: ScopeTypeConsumer},
|
||||
{Code: "consumer:account:write", Name: "管理账户", Type: ScopeTypeConsumer},
|
||||
{Code: "consumer:apikey:create", Name: "创建API Key", Type: ScopeTypeConsumer},
|
||||
{Code: "consumer:apikey:read", Name: "读取API Key", Type: ScopeTypeConsumer},
|
||||
{Code: "consumer:apikey:revoke", Name: "吊销API Key", Type: ScopeTypeConsumer},
|
||||
{Code: "consumer:usage:read", Name: "读取使用量", Type: ScopeTypeConsumer},
|
||||
|
||||
// Billing Scopes
|
||||
{Code: "billing:read", Name: "读取账单", Type: ScopeTypeBilling},
|
||||
{Code: "billing:write", Name: "修改账单设置", Type: ScopeTypeBilling},
|
||||
|
||||
// Router Scopes
|
||||
{Code: "router:invoke", Name: "调用模型", Type: ScopeTypeRouter},
|
||||
{Code: "router:model:list", Name: "列出可用模型", Type: ScopeTypeRouter},
|
||||
{Code: "router:model:config", Name: "配置路由策略", Type: ScopeTypeRouter},
|
||||
|
||||
// Wildcard Scope
|
||||
{Code: "*", Name: "通配符", Type: ScopeTypePlatform},
|
||||
}
|
||||
|
||||
// GetPredefinedScopeByCode 根据Code获取预定义Scope
|
||||
func GetPredefinedScopeByCode(code string) *Scope {
|
||||
for _, scope := range PredefinedScopes {
|
||||
if scope.Code == code {
|
||||
return scope
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPredefinedScope 检查是否为预定义Scope
|
||||
func IsPredefinedScope(code string) bool {
|
||||
return GetPredefinedScopeByCode(code) != nil
|
||||
}
|
||||
247
supply-api/internal/iam/model/scope_test.go
Normal file
247
supply-api/internal/iam/model/scope_test.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestScopeModel_NewScope_ValidInput 测试创建Scope - 有效输入
|
||||
func TestScopeModel_NewScope_ValidInput(t *testing.T) {
|
||||
// arrange
|
||||
scopeCode := "platform:read"
|
||||
scopeName := "读取平台配置"
|
||||
scopeType := "platform"
|
||||
|
||||
// act
|
||||
scope := NewScope(scopeCode, scopeName, scopeType)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, scopeCode, scope.Code)
|
||||
assert.Equal(t, scopeName, scope.Name)
|
||||
assert.Equal(t, scopeType, scope.Type)
|
||||
assert.True(t, scope.IsActive)
|
||||
assert.NotEmpty(t, scope.RequestID)
|
||||
assert.Equal(t, 1, scope.Version)
|
||||
}
|
||||
|
||||
// TestScopeModel_ScopeCategories 测试Scope分类
|
||||
func TestScopeModel_ScopeCategories(t *testing.T) {
|
||||
// arrange & act
|
||||
testCases := []struct {
|
||||
scopeCode string
|
||||
expectedType string
|
||||
}{
|
||||
// platform:* 分类
|
||||
{"platform:read", ScopeTypePlatform},
|
||||
{"platform:write", ScopeTypePlatform},
|
||||
{"platform:admin", ScopeTypePlatform},
|
||||
{"platform:audit:read", ScopeTypePlatform},
|
||||
{"platform:audit:export", ScopeTypePlatform},
|
||||
|
||||
// tenant:* 分类
|
||||
{"tenant:read", ScopeTypePlatform},
|
||||
{"tenant:write", ScopeTypePlatform},
|
||||
{"tenant:member:manage", ScopeTypePlatform},
|
||||
|
||||
// supply:* 分类
|
||||
{"supply:account:read", ScopeTypeSupply},
|
||||
{"supply:account:write", ScopeTypeSupply},
|
||||
{"supply:package:read", ScopeTypeSupply},
|
||||
{"supply:package:write", ScopeTypeSupply},
|
||||
|
||||
// consumer:* 分类
|
||||
{"consumer:account:read", ScopeTypeConsumer},
|
||||
{"consumer:apikey:create", ScopeTypeConsumer},
|
||||
|
||||
// billing:* 分类
|
||||
{"billing:read", ScopeTypePlatform},
|
||||
|
||||
// router:* 分类
|
||||
{"router:invoke", ScopeTypeRouter},
|
||||
{"router:model:list", ScopeTypeRouter},
|
||||
}
|
||||
|
||||
// assert
|
||||
for _, tc := range testCases {
|
||||
scope := NewScope(tc.scopeCode, tc.scopeCode, tc.expectedType)
|
||||
assert.Equal(t, tc.expectedType, scope.Type, "scope %s should be type %s", tc.scopeCode, tc.expectedType)
|
||||
}
|
||||
}
|
||||
|
||||
// TestScopeModel_NewScope_DefaultFields 测试创建Scope - 默认字段
|
||||
func TestScopeModel_NewScope_DefaultFields(t *testing.T) {
|
||||
// arrange
|
||||
scopeCode := "tenant:read"
|
||||
scopeName := "读取租户信息"
|
||||
scopeType := ScopeTypePlatform
|
||||
|
||||
// act
|
||||
scope := NewScope(scopeCode, scopeName, scopeType)
|
||||
|
||||
// assert - 验证默认字段
|
||||
assert.Equal(t, 1, scope.Version, "version should default to 1")
|
||||
assert.NotEmpty(t, scope.RequestID, "request_id should be auto-generated")
|
||||
assert.True(t, scope.IsActive, "is_active should default to true")
|
||||
}
|
||||
|
||||
// TestScopeModel_NewScope_WithRequestID 测试创建Scope - 指定RequestID
|
||||
func TestScopeModel_NewScope_WithRequestID(t *testing.T) {
|
||||
// arrange
|
||||
requestID := "req-54321"
|
||||
|
||||
// act
|
||||
scope := NewScopeWithRequestID("platform:read", "读取平台配置", ScopeTypePlatform, requestID)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, requestID, scope.RequestID)
|
||||
}
|
||||
|
||||
// TestScopeModel_NewScope_AuditFields 测试创建Scope - 审计字段
|
||||
func TestScopeModel_NewScope_AuditFields(t *testing.T) {
|
||||
// arrange
|
||||
createdIP := "10.0.0.1"
|
||||
updatedIP := "10.0.0.2"
|
||||
|
||||
// act
|
||||
scope := NewScopeWithAudit("billing:read", "读取账单", ScopeTypePlatform, "req-789", createdIP, updatedIP)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, createdIP, scope.CreatedIP)
|
||||
assert.Equal(t, updatedIP, scope.UpdatedIP)
|
||||
assert.Equal(t, 1, scope.Version)
|
||||
}
|
||||
|
||||
// TestScopeModel_Activate 测试激活Scope
|
||||
func TestScopeModel_Activate(t *testing.T) {
|
||||
// arrange
|
||||
scope := NewScope("test:scope", "测试Scope", ScopeTypePlatform)
|
||||
scope.IsActive = false
|
||||
|
||||
// act
|
||||
scope.Activate()
|
||||
|
||||
// assert
|
||||
assert.True(t, scope.IsActive)
|
||||
}
|
||||
|
||||
// TestScopeModel_Deactivate 测试停用Scope
|
||||
func TestScopeModel_Deactivate(t *testing.T) {
|
||||
// arrange
|
||||
scope := NewScope("test:scope", "测试Scope", ScopeTypePlatform)
|
||||
|
||||
// act
|
||||
scope.Deactivate()
|
||||
|
||||
// assert
|
||||
assert.False(t, scope.IsActive)
|
||||
}
|
||||
|
||||
// TestScopeModel_IncrementVersion 测试版本号递增
|
||||
func TestScopeModel_IncrementVersion(t *testing.T) {
|
||||
// arrange
|
||||
scope := NewScope("test:scope", "测试Scope", ScopeTypePlatform)
|
||||
originalVersion := scope.Version
|
||||
|
||||
// act
|
||||
scope.IncrementVersion()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, originalVersion+1, scope.Version)
|
||||
}
|
||||
|
||||
// TestScopeModel_ScopeType_Platform 测试平台Scope类型
|
||||
func TestScopeModel_ScopeType_Platform(t *testing.T) {
|
||||
// arrange & act
|
||||
scope := NewScope("platform:admin", "平台管理", ScopeTypePlatform)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, ScopeTypePlatform, scope.Type)
|
||||
}
|
||||
|
||||
// TestScopeModel_ScopeType_Supply 测试供应方Scope类型
|
||||
func TestScopeModel_ScopeType_Supply(t *testing.T) {
|
||||
// arrange & act
|
||||
scope := NewScope("supply:account:write", "管理供应账号", ScopeTypeSupply)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, ScopeTypeSupply, scope.Type)
|
||||
}
|
||||
|
||||
// TestScopeModel_ScopeType_Consumer 测试需求方Scope类型
|
||||
func TestScopeModel_ScopeType_Consumer(t *testing.T) {
|
||||
// arrange & act
|
||||
scope := NewScope("consumer:apikey:create", "创建API Key", ScopeTypeConsumer)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, ScopeTypeConsumer, scope.Type)
|
||||
}
|
||||
|
||||
// TestScopeModel_ScopeType_Router 测试路由Scope类型
|
||||
func TestScopeModel_ScopeType_Router(t *testing.T) {
|
||||
// arrange & act
|
||||
scope := NewScope("router:invoke", "调用模型", ScopeTypeRouter)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, ScopeTypeRouter, scope.Type)
|
||||
}
|
||||
|
||||
// TestScopeModel_NewScope_EmptyCode 测试创建Scope - 空Scope代码(应返回错误)
|
||||
func TestScopeModel_NewScope_EmptyCode(t *testing.T) {
|
||||
// arrange & act
|
||||
scope, err := NewScopeWithValidation("", "测试Scope", ScopeTypePlatform)
|
||||
|
||||
// assert
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, scope)
|
||||
assert.Equal(t, ErrInvalidScopeCode, err)
|
||||
}
|
||||
|
||||
// TestScopeModel_NewScope_InvalidScopeType 测试创建Scope - 无效Scope类型
|
||||
func TestScopeModel_NewScope_InvalidScopeType(t *testing.T) {
|
||||
// arrange & act
|
||||
scope, err := NewScopeWithValidation("test:scope", "测试Scope", "invalid_type")
|
||||
|
||||
// assert
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, scope)
|
||||
assert.Equal(t, ErrInvalidScopeType, err)
|
||||
}
|
||||
|
||||
// TestScopeModel_ToScopeInfo 测试Scope转换为ScopeInfo
|
||||
func TestScopeModel_ToScopeInfo(t *testing.T) {
|
||||
// arrange
|
||||
scope := NewScope("platform:read", "读取平台配置", ScopeTypePlatform)
|
||||
scope.ID = 1
|
||||
|
||||
// act
|
||||
scopeInfo := scope.ToScopeInfo()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, "platform:read", scopeInfo.ScopeCode)
|
||||
assert.Equal(t, "读取平台配置", scopeInfo.ScopeName)
|
||||
assert.Equal(t, ScopeTypePlatform, scopeInfo.ScopeType)
|
||||
assert.True(t, scopeInfo.IsActive)
|
||||
}
|
||||
|
||||
// TestScopeModel_GetScopeTypeFromCode 测试从Scope Code推断类型
|
||||
func TestScopeModel_GetScopeTypeFromCode(t *testing.T) {
|
||||
// arrange & act & assert
|
||||
assert.Equal(t, ScopeTypePlatform, GetScopeTypeFromCode("platform:read"))
|
||||
assert.Equal(t, ScopeTypePlatform, GetScopeTypeFromCode("tenant:read"))
|
||||
assert.Equal(t, ScopeTypeSupply, GetScopeTypeFromCode("supply:account:read"))
|
||||
assert.Equal(t, ScopeTypeConsumer, GetScopeTypeFromCode("consumer:apikey:read"))
|
||||
assert.Equal(t, ScopeTypeRouter, GetScopeTypeFromCode("router:invoke"))
|
||||
assert.Equal(t, ScopeTypePlatform, GetScopeTypeFromCode("billing:read"))
|
||||
}
|
||||
|
||||
// TestScopeModel_IsWildcardScope 测试通配符Scope
|
||||
func TestScopeModel_IsWildcardScope(t *testing.T) {
|
||||
// arrange
|
||||
wildcardScope := NewScope("*", "通配符", ScopeTypePlatform)
|
||||
normalScope := NewScope("platform:read", "读取平台配置", ScopeTypePlatform)
|
||||
|
||||
// assert
|
||||
assert.True(t, wildcardScope.IsWildcard())
|
||||
assert.False(t, normalScope.IsWildcard())
|
||||
}
|
||||
172
supply-api/internal/iam/model/user_role.go
Normal file
172
supply-api/internal/iam/model/user_role.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// UserRoleMapping 用户-角色关联模型
|
||||
// 对应数据库 iam_user_roles 表
|
||||
type UserRoleMapping struct {
|
||||
ID int64 // 主键ID
|
||||
UserID int64 // 用户ID
|
||||
RoleID int64 // 角色ID (FK -> iam_roles.id)
|
||||
TenantID int64 // 租户范围(NULL表示全局,0也代表全局)
|
||||
GrantedBy int64 // 授权人ID
|
||||
ExpiresAt *time.Time // 角色过期时间(nil表示永不过期)
|
||||
IsActive bool // 是否激活
|
||||
|
||||
// 审计字段
|
||||
RequestID string // 请求追踪ID
|
||||
CreatedIP string // 创建者IP
|
||||
UpdatedIP string // 更新者IP
|
||||
Version int // 乐观锁版本号
|
||||
|
||||
// 时间戳
|
||||
CreatedAt *time.Time // 创建时间
|
||||
UpdatedAt *time.Time // 更新时间
|
||||
GrantedAt *time.Time // 授权时间
|
||||
}
|
||||
|
||||
// NewUserRoleMapping 创建新的用户-角色映射
|
||||
func NewUserRoleMapping(userID, roleID, tenantID int64) *UserRoleMapping {
|
||||
now := time.Now()
|
||||
return &UserRoleMapping{
|
||||
UserID: userID,
|
||||
RoleID: roleID,
|
||||
TenantID: tenantID,
|
||||
IsActive: true,
|
||||
RequestID: generateRequestID(),
|
||||
Version: 1,
|
||||
CreatedAt: &now,
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
}
|
||||
|
||||
// NewUserRoleMappingWithGrant 创建带授权信息的用户-角色映射
|
||||
func NewUserRoleMappingWithGrant(userID, roleID, tenantID, grantedBy int64, expiresAt *time.Time) *UserRoleMapping {
|
||||
now := time.Now()
|
||||
return &UserRoleMapping{
|
||||
UserID: userID,
|
||||
RoleID: roleID,
|
||||
TenantID: tenantID,
|
||||
GrantedBy: grantedBy,
|
||||
ExpiresAt: expiresAt,
|
||||
GrantedAt: &now,
|
||||
IsActive: true,
|
||||
RequestID: generateRequestID(),
|
||||
Version: 1,
|
||||
CreatedAt: &now,
|
||||
UpdatedAt: &now,
|
||||
}
|
||||
}
|
||||
|
||||
// HasRole 检查用户是否拥有指定角色
|
||||
func (m *UserRoleMapping) HasRole(roleID int64) bool {
|
||||
return m.RoleID == roleID && m.IsActive
|
||||
}
|
||||
|
||||
// IsGlobalRole 检查是否为全局角色(租户ID为0或nil)
|
||||
func (m *UserRoleMapping) IsGlobalRole() bool {
|
||||
return m.TenantID == 0
|
||||
}
|
||||
|
||||
// IsExpired 检查角色是否已过期
|
||||
func (m *UserRoleMapping) IsExpired() bool {
|
||||
if m.ExpiresAt == nil {
|
||||
return false // 永不过期
|
||||
}
|
||||
return time.Now().After(*m.ExpiresAt)
|
||||
}
|
||||
|
||||
// IsValid 检查角色分配是否有效(激活且未过期)
|
||||
func (m *UserRoleMapping) IsValid() bool {
|
||||
return m.IsActive && !m.IsExpired()
|
||||
}
|
||||
|
||||
// Revoke 撤销角色分配
|
||||
func (m *UserRoleMapping) Revoke() {
|
||||
m.IsActive = false
|
||||
m.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// Grant 重新授予角色
|
||||
func (m *UserRoleMapping) Grant() {
|
||||
m.IsActive = true
|
||||
m.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// IncrementVersion 递增版本号
|
||||
func (m *UserRoleMapping) IncrementVersion() {
|
||||
m.Version++
|
||||
m.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// ExtendExpiration 延长过期时间
|
||||
func (m *UserRoleMapping) ExtendExpiration(newExpiresAt *time.Time) {
|
||||
m.ExpiresAt = newExpiresAt
|
||||
m.UpdatedAt = nowPtr()
|
||||
}
|
||||
|
||||
// UserRoleMappingInfo 用户-角色映射信息(用于API响应)
|
||||
type UserRoleMappingInfo struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
RoleID int64 `json:"role_id"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
ExpiresAt *string `json:"expires_at,omitempty"`
|
||||
}
|
||||
|
||||
// ToInfo 转换为映射信息
|
||||
func (m *UserRoleMapping) ToInfo() *UserRoleMappingInfo {
|
||||
info := &UserRoleMappingInfo{
|
||||
UserID: m.UserID,
|
||||
RoleID: m.RoleID,
|
||||
TenantID: m.TenantID,
|
||||
IsActive: m.IsActive,
|
||||
}
|
||||
if m.ExpiresAt != nil {
|
||||
expStr := m.ExpiresAt.Format(time.RFC3339)
|
||||
info.ExpiresAt = &expStr
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// UserRoleAssignmentInfo 用户角色分配详情(用于API响应)
|
||||
type UserRoleAssignmentInfo struct {
|
||||
UserID int64 `json:"user_id"`
|
||||
RoleCode string `json:"role_code"`
|
||||
RoleName string `json:"role_name"`
|
||||
TenantID int64 `json:"tenant_id"`
|
||||
GrantedBy int64 `json:"granted_by"`
|
||||
GrantedAt string `json:"granted_at"`
|
||||
ExpiresAt string `json:"expires_at,omitempty"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsExpired bool `json:"is_expired"`
|
||||
}
|
||||
|
||||
// UserRoleWithDetails 用户角色分配(含角色详情)
|
||||
type UserRoleWithDetails struct {
|
||||
*UserRoleMapping
|
||||
RoleCode string
|
||||
RoleName string
|
||||
}
|
||||
|
||||
// ToAssignmentInfo 转换为分配详情
|
||||
func (m *UserRoleWithDetails) ToAssignmentInfo() *UserRoleAssignmentInfo {
|
||||
info := &UserRoleAssignmentInfo{
|
||||
UserID: m.UserID,
|
||||
RoleCode: m.RoleCode,
|
||||
RoleName: m.RoleName,
|
||||
TenantID: m.TenantID,
|
||||
GrantedBy: m.GrantedBy,
|
||||
IsActive: m.IsActive,
|
||||
IsExpired: m.IsExpired(),
|
||||
}
|
||||
if m.GrantedAt != nil {
|
||||
info.GrantedAt = m.GrantedAt.Format(time.RFC3339)
|
||||
}
|
||||
if m.ExpiresAt != nil {
|
||||
info.ExpiresAt = m.ExpiresAt.Format(time.RFC3339)
|
||||
}
|
||||
return info
|
||||
}
|
||||
254
supply-api/internal/iam/model/user_role_test.go
Normal file
254
supply-api/internal/iam/model/user_role_test.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestUserRoleMapping_AssignRole 测试分配角色
|
||||
func TestUserRoleMapping_AssignRole(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
tenantID := int64(1)
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMapping(userID, roleID, tenantID)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, userID, userRole.UserID)
|
||||
assert.Equal(t, roleID, userRole.RoleID)
|
||||
assert.Equal(t, tenantID, userRole.TenantID)
|
||||
assert.True(t, userRole.IsActive)
|
||||
assert.NotEmpty(t, userRole.RequestID)
|
||||
assert.Equal(t, 1, userRole.Version)
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_HasRole 测试用户是否拥有角色
|
||||
func TestUserRoleMapping_HasRole(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
role := NewRole("org_admin", "组织管理员", RoleTypePlatform, 50)
|
||||
role.ID = 1
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMapping(userID, role.ID, 0) // 0 表示全局角色
|
||||
|
||||
// assert
|
||||
assert.True(t, userRole.HasRole(role.ID))
|
||||
assert.False(t, userRole.HasRole(999)) // 不存在的角色ID
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_GlobalRole 测试全局角色(tenantID为0)
|
||||
func TestUserRoleMapping_GlobalRole(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
|
||||
// act - 全局角色
|
||||
userRole := NewUserRoleMapping(userID, roleID, 0)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, int64(0), userRole.TenantID)
|
||||
assert.True(t, userRole.IsGlobalRole())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_TenantRole 测试租户角色
|
||||
func TestUserRoleMapping_TenantRole(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
tenantID := int64(123)
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMapping(userID, roleID, tenantID)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, tenantID, userRole.TenantID)
|
||||
assert.False(t, userRole.IsGlobalRole())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_WithGrantInfo 测试带授权信息的分配
|
||||
func TestUserRoleMapping_WithGrantInfo(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
tenantID := int64(1)
|
||||
grantedBy := int64(1)
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMappingWithGrant(userID, roleID, tenantID, grantedBy, &expiresAt)
|
||||
|
||||
// assert
|
||||
assert.Equal(t, userID, userRole.UserID)
|
||||
assert.Equal(t, roleID, userRole.RoleID)
|
||||
assert.Equal(t, grantedBy, userRole.GrantedBy)
|
||||
assert.NotNil(t, userRole.ExpiresAt)
|
||||
assert.NotNil(t, userRole.GrantedAt)
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_Expired 测试过期角色
|
||||
func TestUserRoleMapping_Expired(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
expiresAt := time.Now().Add(-1 * time.Hour) // 已过期
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMappingWithGrant(userID, roleID, 0, 1, &expiresAt)
|
||||
|
||||
// assert
|
||||
assert.True(t, userRole.IsExpired())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_NotExpired 测试未过期角色
|
||||
func TestUserRoleMapping_NotExpired(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
expiresAt := time.Now().Add(24 * time.Hour) // 未过期
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMappingWithGrant(userID, roleID, 0, 1, &expiresAt)
|
||||
|
||||
// assert
|
||||
assert.False(t, userRole.IsExpired())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_NoExpiration 测试永不过期角色
|
||||
func TestUserRoleMapping_NoExpiration(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
|
||||
// act
|
||||
userRole := NewUserRoleMapping(userID, roleID, 0)
|
||||
|
||||
// assert
|
||||
assert.Nil(t, userRole.ExpiresAt)
|
||||
assert.False(t, userRole.IsExpired())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_Revoke 测试撤销角色
|
||||
func TestUserRoleMapping_Revoke(t *testing.T) {
|
||||
// arrange
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
|
||||
// act
|
||||
userRole.Revoke()
|
||||
|
||||
// assert
|
||||
assert.False(t, userRole.IsActive)
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_Grant 测试重新授予角色
|
||||
func TestUserRoleMapping_Grant(t *testing.T) {
|
||||
// arrange
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
userRole.Revoke()
|
||||
|
||||
// act
|
||||
userRole.Grant()
|
||||
|
||||
// assert
|
||||
assert.True(t, userRole.IsActive)
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_IncrementVersion 测试版本号递增
|
||||
func TestUserRoleMapping_IncrementVersion(t *testing.T) {
|
||||
// arrange
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
originalVersion := userRole.Version
|
||||
|
||||
// act
|
||||
userRole.IncrementVersion()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, originalVersion+1, userRole.Version)
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_Valid 测试有效角色
|
||||
func TestUserRoleMapping_Valid(t *testing.T) {
|
||||
// arrange - 活跃且未过期的角色
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
userRole.ExpiresAt = &expiresAt
|
||||
|
||||
// act & assert
|
||||
assert.True(t, userRole.IsValid())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_InvalidInactive 测试无效角色 - 未激活
|
||||
func TestUserRoleMapping_InvalidInactive(t *testing.T) {
|
||||
// arrange
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
userRole.Revoke()
|
||||
|
||||
// assert
|
||||
assert.False(t, userRole.IsValid())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_Valid_ExpiredButActive 测试过期但激活的角色
|
||||
func TestUserRoleMapping_Valid_ExpiredButActive(t *testing.T) {
|
||||
// arrange - 已过期但仍然激活的角色(应该无效)
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
expiresAt := time.Now().Add(-1 * time.Hour)
|
||||
userRole.ExpiresAt = &expiresAt
|
||||
|
||||
// assert - 即使IsActive为true,过期角色也应该无效
|
||||
assert.False(t, userRole.IsValid())
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_UniqueConstraint 测试唯一性约束
|
||||
func TestUserRoleMapping_UniqueConstraint(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
tenantID := int64(0) // 全局角色
|
||||
|
||||
// act
|
||||
userRole1 := NewUserRoleMapping(userID, roleID, tenantID)
|
||||
userRole2 := NewUserRoleMapping(userID, roleID, tenantID)
|
||||
|
||||
// assert - 同一个用户、角色、租户组合应该唯一
|
||||
assert.Equal(t, userRole1.UserID, userRole2.UserID)
|
||||
assert.Equal(t, userRole1.RoleID, userRole2.RoleID)
|
||||
assert.Equal(t, userRole1.TenantID, userRole2.TenantID)
|
||||
}
|
||||
|
||||
// TestUserRoleMapping_DifferentTenants 测试不同租户可以有相同角色
|
||||
func TestUserRoleMapping_DifferentTenants(t *testing.T) {
|
||||
// arrange
|
||||
userID := int64(100)
|
||||
roleID := int64(1)
|
||||
tenantID1 := int64(1)
|
||||
tenantID2 := int64(2)
|
||||
|
||||
// act
|
||||
userRole1 := NewUserRoleMapping(userID, roleID, tenantID1)
|
||||
userRole2 := NewUserRoleMapping(userID, roleID, tenantID2)
|
||||
|
||||
// assert - 不同租户的角色分配互不影响
|
||||
assert.Equal(t, tenantID1, userRole1.TenantID)
|
||||
assert.Equal(t, tenantID2, userRole2.TenantID)
|
||||
assert.NotEqual(t, userRole1.TenantID, userRole2.TenantID)
|
||||
}
|
||||
|
||||
// TestUserRoleMappingInfo_ToInfo 测试转换为UserRoleMappingInfo
|
||||
func TestUserRoleMappingInfo_ToInfo(t *testing.T) {
|
||||
// arrange
|
||||
userRole := NewUserRoleMapping(100, 1, 0)
|
||||
userRole.ID = 1
|
||||
|
||||
// act
|
||||
info := userRole.ToInfo()
|
||||
|
||||
// assert
|
||||
assert.Equal(t, int64(100), info.UserID)
|
||||
assert.Equal(t, int64(1), info.RoleID)
|
||||
assert.Equal(t, int64(0), info.TenantID)
|
||||
assert.True(t, info.IsActive)
|
||||
}
|
||||
Reference in New Issue
Block a user