mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Admin/AuthorizationPropertyTests.cs
zpc 6bf2ea595c feat(admin-business): 完成后台管理系统全部业务模块
- 系统配置管理模块 (Config)
- 内容管理模块 (Banner, Promotion)
- 测评管理模块 (Type, Question, Category, Mapping, Conclusion)
- 用户管理模块 (User)
- 订单管理模块 (Order)
- 规划师管理模块 (Planner)
- 分销管理模块 (InviteCode, Commission, Withdrawal)
- 数据统计仪表盘模块 (Dashboard)
- 权限控制集成
- 服务注册配置

全部381个测试通过
2026-02-03 20:50:51 +08:00

447 lines
15 KiB
C#

using System.Reflection;
using FsCheck;
using FsCheck.Xunit;
using MiAssessment.Admin.Business.Attributes;
using MiAssessment.Admin.Business.Constants;
using MiAssessment.Admin.Business.Controllers;
using Microsoft.AspNetCore.Mvc;
using Xunit;
namespace MiAssessment.Tests.Admin;
/// <summary>
/// 权限控制属性测试
/// 验证业务控制器的权限配置正确性
///
/// Property 16: Authorization Enforcement
/// *For any* protected endpoint and any user without the required permission,
/// the system SHALL return HTTP 403 Forbidden.
///
/// **Validates: Requirements 18.2**
/// </summary>
public class AuthorizationPropertyTests
{
/// <summary>
/// 获取所有业务控制器类型
/// </summary>
private static readonly Type[] BusinessControllerTypes = typeof(BusinessControllerBase)
.Assembly
.GetTypes()
.Where(t => t.IsClass
&& !t.IsAbstract
&& t.IsSubclassOf(typeof(BusinessControllerBase))
&& t != typeof(BusinessControllerBase))
.ToArray();
/// <summary>
/// 获取所有需要权限控制的控制器(排除健康检查等公开接口)
/// </summary>
private static readonly string[] ExcludedControllers = new[]
{
"HealthController",
"UploadController" // 上传接口可能有单独的认证机制
};
/// <summary>
/// 某些公开接口不需要权限控制(如获取配置键列表等)
/// 注意:所有业务接口现在都已添加权限控制
/// </summary>
private static readonly HashSet<string> ExcludedMethods = new()
{
// 所有业务接口都已添加权限控制,无需排除
};
/// <summary>
/// 获取所有需要权限控制的业务控制器
/// </summary>
private static IEnumerable<Type> GetProtectedControllers()
{
return BusinessControllerTypes
.Where(t => !ExcludedControllers.Contains(t.Name));
}
/// <summary>
/// 获取控制器的所有公开 Action 方法
/// </summary>
private static IEnumerable<MethodInfo> GetActionMethods(Type controllerType)
{
return controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => !m.IsSpecialName) // 排除属性的 getter/setter
.Where(m => m.GetCustomAttributes<HttpGetAttribute>().Any()
|| m.GetCustomAttributes<HttpPostAttribute>().Any()
|| m.GetCustomAttributes<HttpPutAttribute>().Any()
|| m.GetCustomAttributes<HttpDeleteAttribute>().Any());
}
/// <summary>
/// 检查方法是否在排除列表中
/// </summary>
private static bool IsExcludedMethod(Type controllerType, MethodInfo method)
{
return ExcludedMethods.Contains($"{controllerType.Name}.{method.Name}");
}
#region Property 16: Authorization Enforcement
/// <summary>
/// Property 16: 所有业务控制器的 Action 方法都应该有 BusinessPermission 特性
/// *For any* business controller action method, it SHALL have a [BusinessPermission] attribute.
///
/// 注意:此测试会检测缺少权限特性的方法,这些方法需要添加权限控制。
///
/// **Validates: Requirements 18.2**
/// </summary>
[Fact]
public void AllBusinessControllerActions_ShouldHaveBusinessPermissionAttribute()
{
// Arrange
var methodsWithoutPermission = new List<string>();
// Act
foreach (var controllerType in GetProtectedControllers())
{
var actionMethods = GetActionMethods(controllerType);
foreach (var method in actionMethods)
{
// 跳过排除的方法
if (IsExcludedMethod(controllerType, method))
continue;
var hasPermissionAttribute = method.GetCustomAttributes<BusinessPermissionAttribute>().Any();
if (!hasPermissionAttribute)
{
methodsWithoutPermission.Add($"{controllerType.Name}.{method.Name}");
}
}
}
// Assert
Assert.True(
methodsWithoutPermission.Count == 0,
$"以下 Action 方法缺少 [BusinessPermission] 特性:\n{string.Join("\n", methodsWithoutPermission)}");
}
/// <summary>
/// Property 16: 权限编码格式验证
/// *For any* BusinessPermission attribute, the permission code SHALL follow the format "module:action"
/// or "module:submodule:action".
///
/// **Validates: Requirements 18.2**
/// </summary>
[Fact]
public void AllPermissionCodes_ShouldFollowCorrectFormat()
{
// Arrange
var invalidPermissionCodes = new List<string>();
// 支持 module:action 或 module:submodule:action 格式
var permissionCodePattern = @"^[a-z]+:[a-z]+(:[a-z]+)?$";
// Act
foreach (var controllerType in GetProtectedControllers())
{
var actionMethods = GetActionMethods(controllerType);
foreach (var method in actionMethods)
{
var permissionAttributes = method.GetCustomAttributes<BusinessPermissionAttribute>();
foreach (var attr in permissionAttributes)
{
if (string.IsNullOrWhiteSpace(attr.PermissionCode))
{
invalidPermissionCodes.Add($"{controllerType.Name}.{method.Name}: 权限编码为空");
}
else if (!System.Text.RegularExpressions.Regex.IsMatch(attr.PermissionCode, permissionCodePattern))
{
invalidPermissionCodes.Add($"{controllerType.Name}.{method.Name}: '{attr.PermissionCode}' 不符合 'module:action' 或 'module:submodule:action' 格式");
}
}
}
}
// Assert
Assert.True(
invalidPermissionCodes.Count == 0,
$"以下权限编码格式不正确:\n{string.Join("\n", invalidPermissionCodes)}");
}
/// <summary>
/// Property 16: 所有定义的权限都应该是有效的
/// *For any* permission defined in BusinessPermissions.cs, it SHALL have a valid format and non-empty values.
///
/// **Validates: Requirements 18.2**
/// </summary>
[Fact]
public void AllDefinedPermissions_ShouldBeValid()
{
// Arrange
var invalidPermissions = new List<string>();
// 支持 module:action 或 module:submodule:action 格式
var permissionCodePattern = @"^[a-z]+:[a-z]+(:[a-z]+)?$";
// Act
foreach (var permission in BusinessPermissions.AllPermissions)
{
// 验证权限编码
if (string.IsNullOrWhiteSpace(permission.Code))
{
invalidPermissions.Add($"权限编码为空: Name={permission.Name}");
}
else if (!System.Text.RegularExpressions.Regex.IsMatch(permission.Code, permissionCodePattern))
{
invalidPermissions.Add($"权限编码格式不正确: Code={permission.Code}");
}
// 验证权限名称
if (string.IsNullOrWhiteSpace(permission.Name))
{
invalidPermissions.Add($"权限名称为空: Code={permission.Code}");
}
// 验证模块名称
if (string.IsNullOrWhiteSpace(permission.Module))
{
invalidPermissions.Add($"模块名称为空: Code={permission.Code}");
}
// 验证权限编码与模块名称一致性
if (!string.IsNullOrWhiteSpace(permission.Code) && !string.IsNullOrWhiteSpace(permission.Module))
{
var codeModule = permission.Code.Split(':')[0];
if (codeModule != permission.Module)
{
invalidPermissions.Add($"权限编码模块与 Module 不一致: Code={permission.Code}, Module={permission.Module}");
}
}
}
// Assert
Assert.True(
invalidPermissions.Count == 0,
$"以下权限定义无效:\n{string.Join("\n", invalidPermissions)}");
}
/// <summary>
/// Property 16: 权限编码应该唯一
/// *For any* two permissions in BusinessPermissions.AllPermissions, their codes SHALL be unique.
///
/// **Validates: Requirements 18.2**
/// </summary>
[Fact]
public void AllPermissionCodes_ShouldBeUnique()
{
// Arrange
var allCodes = BusinessPermissions.AllPermissions.Select(p => p.Code).ToList();
var duplicateCodes = allCodes
.GroupBy(c => c)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
// Assert
Assert.True(
duplicateCodes.Count == 0,
$"以下权限编码重复:\n{string.Join("\n", duplicateCodes)}");
}
/// <summary>
/// Property 16: 每个业务模块都应该定义了权限
/// *For any* business module (config, content, assessment, user, order, planner, distribution, dashboard),
/// there SHALL be at least one permission defined.
///
/// **Validates: Requirements 18.3, 18.4**
/// </summary>
[Fact]
public void AllBusinessModules_ShouldHavePermissionsDefined()
{
// Arrange
var expectedModules = new[]
{
"config",
"content",
"assessment",
"user",
"order",
"planner",
"distribution",
"dashboard"
};
var definedModules = BusinessPermissions.AllPermissions
.Select(p => p.Module)
.Distinct()
.ToHashSet();
var missingModules = expectedModules
.Where(m => !definedModules.Contains(m))
.ToList();
// Assert
Assert.True(
missingModules.Count == 0,
$"以下业务模块缺少权限定义:\n{string.Join("\n", missingModules)}");
}
/// <summary>
/// Property 16: 权限粒度验证
/// *For any* module with CRUD operations, it SHALL support view, create, update, delete permissions.
///
/// **Validates: Requirements 18.4**
/// </summary>
[Theory]
[InlineData("content", new[] { "view", "create", "update", "delete" })]
[InlineData("assessment", new[] { "view", "create", "update", "delete" })]
[InlineData("planner", new[] { "view", "create", "update", "delete" })]
public void CrudModules_ShouldHaveFullPermissionGranularity(string module, string[] expectedActions)
{
// Arrange
var modulePermissions = BusinessPermissions.AllPermissions
.Where(p => p.Module == module)
.Select(p => p.Code.Split(':')[1])
.ToHashSet();
var missingActions = expectedActions
.Where(a => !modulePermissions.Contains(a))
.ToList();
// Assert
Assert.True(
missingActions.Count == 0,
$"模块 '{module}' 缺少以下权限操作:\n{string.Join("\n", missingActions.Select(a => $"{module}:{a}"))}");
}
#endregion
#region Property-Based Tests
/// <summary>
/// Property 16: 对于任意业务控制器,其所有 Action 方法的权限编码都应该格式正确
///
/// **Validates: Requirements 18.2**
/// </summary>
[Property(MaxTest = 50)]
public bool AllUsedPermissionCodes_ShouldHaveValidFormat(PositiveInt seed)
{
// Arrange
// 支持 module:action 或 module:submodule:action 格式
var permissionCodePattern = @"^[a-z]+:[a-z]+(:[a-z]+)?$";
var usedPermissionCodes = new HashSet<string>();
// Act: 收集所有使用的权限编码
foreach (var controllerType in GetProtectedControllers())
{
var actionMethods = GetActionMethods(controllerType);
foreach (var method in actionMethods)
{
var permissionAttributes = method.GetCustomAttributes<BusinessPermissionAttribute>();
foreach (var attr in permissionAttributes)
{
if (!string.IsNullOrWhiteSpace(attr.PermissionCode))
{
usedPermissionCodes.Add(attr.PermissionCode);
}
}
}
}
// Assert: 所有使用的权限编码格式都应该正确
foreach (var code in usedPermissionCodes)
{
if (!System.Text.RegularExpressions.Regex.IsMatch(code, permissionCodePattern))
{
return false;
}
}
return true;
}
/// <summary>
/// Property 16: 对于任意已定义的权限,其模块名称应该与权限编码的模块部分一致
///
/// **Validates: Requirements 18.2**
/// </summary>
[Property(MaxTest = 100)]
public bool PermissionModuleConsistency_ShouldHold(PositiveInt index)
{
// Arrange
var permissions = BusinessPermissions.AllPermissions;
if (permissions.Length == 0) return true;
var permissionIndex = index.Get % permissions.Length;
var permission = permissions[permissionIndex];
// Act & Assert
if (string.IsNullOrWhiteSpace(permission.Code) || string.IsNullOrWhiteSpace(permission.Module))
{
return false;
}
var codeModule = permission.Code.Split(':')[0];
return codeModule == permission.Module;
}
#endregion
#region
/// <summary>
/// 验证控制器数量
/// </summary>
[Fact]
public void BusinessControllers_ShouldExist()
{
// Assert: 应该存在业务控制器
Assert.True(
BusinessControllerTypes.Length > 0,
"未找到任何业务控制器");
}
/// <summary>
/// 验证权限定义数量
/// </summary>
[Fact]
public void BusinessPermissions_ShouldExist()
{
// Assert: 应该存在权限定义
Assert.True(
BusinessPermissions.AllPermissions.Length > 0,
"未找到任何权限定义");
}
/// <summary>
/// 列出所有业务控制器及其 Action 方法(用于调试)
/// </summary>
[Fact]
public void ListAllBusinessControllerActions()
{
var controllerActions = new List<string>();
foreach (var controllerType in BusinessControllerTypes)
{
var actionMethods = GetActionMethods(controllerType);
foreach (var method in actionMethods)
{
var permissionAttrs = method.GetCustomAttributes<BusinessPermissionAttribute>().ToList();
var permissionCodes = permissionAttrs.Any()
? string.Join(", ", permissionAttrs.Select(a => a.PermissionCode))
: "无权限特性";
controllerActions.Add($"{controllerType.Name}.{method.Name}: [{permissionCodes}]");
}
}
// 输出所有控制器和方法(用于调试)
// 这个测试总是通过,只是用于查看当前状态
Assert.True(true, $"业务控制器 Action 列表:\n{string.Join("\n", controllerActions)}");
}
#endregion
}