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:
432
supply-api/internal/iam/service/iam_service_test.go
Normal file
432
supply-api/internal/iam/service/iam_service_test.go
Normal file
@@ -0,0 +1,432 @@
|
||||
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")
|
||||
}
|
||||
Reference in New Issue
Block a user