- 系统配置管理模块 (Config) - 内容管理模块 (Banner, Promotion) - 测评管理模块 (Type, Question, Category, Mapping, Conclusion) - 用户管理模块 (User) - 订单管理模块 (Order) - 规划师管理模块 (Planner) - 分销管理模块 (InviteCode, Commission, Withdrawal) - 数据统计仪表盘模块 (Dashboard) - 权限控制集成 - 服务注册配置 全部381个测试通过
447 lines
15 KiB
C#
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
|
|
}
|