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; /// /// 权限验证属性测试 /// Property 18: Authentication Enforcement - 所有业务控制器方法都需要认证 /// Property 19: Permission Enforcement - 所有业务控制器方法都有权限标记 /// 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) }; /// /// Property 18: 所有业务控制器都继承自 BusinessControllerBase /// 验证所有业务控制器都有统一的基类,确保认证机制一致 /// [Fact] public void Property18_AllBusinessControllers_InheritFromBusinessControllerBase() { foreach (var controllerType in BusinessControllerTypes) { Assert.True( typeof(BusinessControllerBase).IsAssignableFrom(controllerType), $"{controllerType.Name} should inherit from BusinessControllerBase" ); } } /// /// Property 18: 所有业务控制器都有 Route 特性 /// 验证所有业务控制器都有正确的路由配置 /// [Fact] public void Property18_AllBusinessControllers_HaveRouteAttribute() { foreach (var controllerType in BusinessControllerTypes) { var routeAttr = controllerType.GetCustomAttribute(); Assert.NotNull(routeAttr); Assert.StartsWith("api/admin/business", routeAttr.Template); } } /// /// Property 19: 所有公开的 Action 方法都有 BusinessPermission 特性 /// 验证权限控制的完整性 /// [Fact] public void Property19_AllPublicActions_HaveBusinessPermissionAttribute() { var exceptions = new List { "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(); Assert.NotNull(permissionAttr); Assert.False(string.IsNullOrEmpty(permissionAttr.PermissionCode), $"{fullName} should have a non-empty permission code"); } } } /// /// Property 19: 权限编码格式正确 (module:action) /// [Fact] public void Property19_PermissionCodes_HaveCorrectFormat() { var allPermissionCodes = new HashSet(); 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(); 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"); } } /// /// Property 19: 验证所有预期的权限编码都已使用 /// [Fact] public void Property19_AllExpectedPermissionCodes_AreUsed() { var expectedPermissions = new HashSet { // 配置模块 "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(); 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(); if (permissionAttr != null) { actualPermissions.Add(permissionAttr.PermissionCode); } } } // 验证所有预期的权限都被使用 foreach (var expected in expectedPermissions) { Assert.Contains(expected, actualPermissions); } } /// /// Property 19: 同一模块的权限应该有一致的前缀 /// [Fact] public void Property19_PermissionCodes_HaveConsistentModulePrefix() { var controllerPermissions = new Dictionary> { { "ConfigController", new HashSet { "config" } }, { "UserController", new HashSet { "user" } }, { "VipController", new HashSet { "vip" } }, { "GoodsController", new HashSet { "goods" } }, { "PrizesController", new HashSet { "goods" } }, // 奖品属于商品模块 { "GoodsTypesController", new HashSet { "goods" } }, // 商品类型属于商品模块 { "OrderController", new HashSet { "order" } }, { "FinanceController", new HashSet { "finance" } }, { "DashboardController", new HashSet { "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(); if (permissionAttr != null) { var module = permissionAttr.PermissionCode.Split(':')[0]; Assert.Contains(module, expectedModules); } } } } }