mi-assessment/server/MiAssessment/tests/MiAssessment.Tests/Services/PermissionPropertyTests.cs
2026-02-03 14:25:01 +08:00

216 lines
8.1 KiB
C#

using FsCheck;
using FsCheck.Xunit;
using MiAssessment.Admin.Business.Attributes;
using MiAssessment.Admin.Business.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using System.Reflection;
using Xunit;
namespace MiAssessment.Tests.Services;
/// <summary>
/// 权限验证属性测试
/// Property 18: Authentication Enforcement - 所有业务控制器方法都需要认证
/// Property 19: Permission Enforcement - 所有业务控制器方法都有权限标记
/// </summary>
public class PermissionPropertyTests
{
private static readonly Type[] BusinessControllerTypes = new[]
{
typeof(ConfigController),
typeof(UserController),
typeof(VipController),
typeof(GoodsController),
typeof(PrizesController),
typeof(GoodsTypesController),
typeof(OrderController),
typeof(FinanceController),
typeof(DashboardController)
};
/// <summary>
/// Property 18: 所有业务控制器都继承自 BusinessControllerBase
/// 验证所有业务控制器都有统一的基类,确保认证机制一致
/// </summary>
[Fact]
public void Property18_AllBusinessControllers_InheritFromBusinessControllerBase()
{
foreach (var controllerType in BusinessControllerTypes)
{
Assert.True(
typeof(BusinessControllerBase).IsAssignableFrom(controllerType),
$"{controllerType.Name} should inherit from BusinessControllerBase"
);
}
}
/// <summary>
/// Property 18: 所有业务控制器都有 Route 特性
/// 验证所有业务控制器都有正确的路由配置
/// </summary>
[Fact]
public void Property18_AllBusinessControllers_HaveRouteAttribute()
{
foreach (var controllerType in BusinessControllerTypes)
{
var routeAttr = controllerType.GetCustomAttribute<RouteAttribute>();
Assert.NotNull(routeAttr);
Assert.StartsWith("api/admin/business", routeAttr.Template);
}
}
/// <summary>
/// Property 19: 所有公开的 Action 方法都有 BusinessPermission 特性
/// 验证权限控制的完整性
/// </summary>
[Fact]
public void Property19_AllPublicActions_HaveBusinessPermissionAttribute()
{
var exceptions = new List<string>
{
"ConfigController.GetConfigKeys" // 获取配置键列表不需要权限
};
foreach (var controllerType in BusinessControllerTypes)
{
var actionMethods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.GetCustomAttributes().Any(a => a is HttpMethodAttribute))
.ToList();
foreach (var method in actionMethods)
{
var fullName = $"{controllerType.Name}.{method.Name}";
if (exceptions.Contains(fullName))
continue;
var permissionAttr = method.GetCustomAttribute<BusinessPermissionAttribute>();
Assert.NotNull(permissionAttr);
Assert.False(string.IsNullOrEmpty(permissionAttr.PermissionCode),
$"{fullName} should have a non-empty permission code");
}
}
}
/// <summary>
/// Property 19: 权限编码格式正确 (module:action)
/// </summary>
[Fact]
public void Property19_PermissionCodes_HaveCorrectFormat()
{
var allPermissionCodes = new HashSet<string>();
foreach (var controllerType in BusinessControllerTypes)
{
var actionMethods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.GetCustomAttributes().Any(a => a is HttpMethodAttribute));
foreach (var method in actionMethods)
{
var permissionAttr = method.GetCustomAttribute<BusinessPermissionAttribute>();
if (permissionAttr != null)
{
allPermissionCodes.Add(permissionAttr.PermissionCode);
}
}
}
foreach (var code in allPermissionCodes)
{
// 权限编码格式: module:action
Assert.Matches(@"^[a-z]+:[a-z_]+$", code);
var parts = code.Split(':');
Assert.Equal(2, parts.Length);
Assert.False(string.IsNullOrEmpty(parts[0]), $"Module part of {code} should not be empty");
Assert.False(string.IsNullOrEmpty(parts[1]), $"Action part of {code} should not be empty");
}
}
/// <summary>
/// Property 19: 验证所有预期的权限编码都已使用
/// </summary>
[Fact]
public void Property19_AllExpectedPermissionCodes_AreUsed()
{
var expectedPermissions = new HashSet<string>
{
// 配置模块
"config:view", "config:edit",
// 用户模块
"user:list", "user:view", "user:money", "user:status", "user:test", "user:clear", "user:gift",
// VIP模块
"vip:list", "vip:edit",
// 商品模块
"goods:list", "goods:view", "goods:add", "goods:edit", "goods:delete", "goods:status",
// 订单模块
"order:list", "order:view", "order:ship", "order:export",
// 财务模块
"finance:view",
// 仪表盘模块
"dashboard:view", "dashboard:edit"
};
var actualPermissions = new HashSet<string>();
foreach (var controllerType in BusinessControllerTypes)
{
var actionMethods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.GetCustomAttributes().Any(a => a is HttpMethodAttribute));
foreach (var method in actionMethods)
{
var permissionAttr = method.GetCustomAttribute<BusinessPermissionAttribute>();
if (permissionAttr != null)
{
actualPermissions.Add(permissionAttr.PermissionCode);
}
}
}
// 验证所有预期的权限都被使用
foreach (var expected in expectedPermissions)
{
Assert.Contains(expected, actualPermissions);
}
}
/// <summary>
/// Property 19: 同一模块的权限应该有一致的前缀
/// </summary>
[Fact]
public void Property19_PermissionCodes_HaveConsistentModulePrefix()
{
var controllerPermissions = new Dictionary<string, HashSet<string>>
{
{ "ConfigController", new HashSet<string> { "config" } },
{ "UserController", new HashSet<string> { "user" } },
{ "VipController", new HashSet<string> { "vip" } },
{ "GoodsController", new HashSet<string> { "goods" } },
{ "PrizesController", new HashSet<string> { "goods" } }, // 奖品属于商品模块
{ "GoodsTypesController", new HashSet<string> { "goods" } }, // 商品类型属于商品模块
{ "OrderController", new HashSet<string> { "order" } },
{ "FinanceController", new HashSet<string> { "finance" } },
{ "DashboardController", new HashSet<string> { "dashboard" } }
};
foreach (var controllerType in BusinessControllerTypes)
{
var expectedModules = controllerPermissions[controllerType.Name];
var actionMethods = controllerType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)
.Where(m => m.GetCustomAttributes().Any(a => a is HttpMethodAttribute));
foreach (var method in actionMethods)
{
var permissionAttr = method.GetCustomAttribute<BusinessPermissionAttribute>();
if (permissionAttr != null)
{
var module = permissionAttr.PermissionCode.Split(':')[0];
Assert.Contains(module, expectedModules);
}
}
}
}
}