AppApiPcMenuGuardTests.cs 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. using System.Reflection;
  2. using Admin.NET.Core;
  3. using Admin.NET.Plugin.AiDOP.Controllers;
  4. using Admin.NET.Plugin.AiDOP.Controllers.S8;
  5. using Admin.NET.Plugin.AiDOP.FinishedWarehouse;
  6. using Admin.NET.Plugin.AiDOP.Infrastructure;
  7. using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
  8. using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
  9. using Admin.NET.Plugin.AiDOP.Order;
  10. using Admin.NET.Plugin.AiDOP.Production;
  11. using Admin.NET.Plugin.ApprovalFlow.Service;
  12. using Xunit;
  13. namespace Admin.NET.Plugin.AiDOP.Tests.Security;
  14. /// <summary>App 业务 API 必须复用 PC 菜单 path 权限,且保留既有业务内层守卫。</summary>
  15. public class AppApiPcMenuGuardTests
  16. {
  17. private const string ApprovalCenter = "/aidop/flowManage/approvalFlowCenter";
  18. [Fact]
  19. public void ApprovalAppEndpoints_RequireApprovalCenterMenu()
  20. {
  21. AssertMethodsGuarded(typeof(FlowTaskService), ApprovalCenter,
  22. "MyPendingPage", "MyDonePage", "MyInitiatedPage", "MyPendingCount",
  23. "Approve", "Reject", "Transfer", "Withdraw", "ReturnToPrev", "AddSign",
  24. "Urge", "BatchApprove", "BatchReject");
  25. AssertMethodsGuarded(typeof(FlowInstanceService), ApprovalCenter, "GetDetail", "GetTimeline");
  26. AssertMethodsGuarded(typeof(ApprovalFlowService), ApprovalCenter, "GetBizTypes");
  27. }
  28. [Fact]
  29. public void AidopQueryEndpoints_RequireCorrespondingPcMenus()
  30. {
  31. Assert.NotNull(typeof(AidopKanbanController).GetMethod("GetDashboardPage")!
  32. .GetCustomAttribute<RequireSmartOpsModuleMenuAttribute>());
  33. AssertMethodsGuarded(typeof(AidopKanbanController), "/aidop/smart-ops/s1",
  34. "GetSmartOpsFilterOptions");
  35. AssertMethodsGuarded(typeof(StockQueryService), "/aidop/s5/inventory/stock-query",
  36. "GetList", "GetTypeOptions", "GetLocationOptions");
  37. AssertMethodsGuarded(typeof(WorkOrderProgressDashboardService),
  38. "/aidop/s2/collaboration-kanban/work-order-progress", "GetList");
  39. AssertMethodsGuarded(typeof(OrderDeliveryService), "/aidop/s1/order-mgmt/orderDelivery",
  40. "GetDeliveryList", "GetDeliveryFlow");
  41. AssertMethodsGuarded(typeof(FqcInspBillService), "/aidop/s7/fqc/task-list", "GetTaskList");
  42. AssertMethodsGuarded(typeof(FqcInspBillService), "/aidop/s7/fqc/result-list", "GetResultList");
  43. AssertMethodsGuarded(typeof(FqcInspBillService), "/aidop/s7/fqc/result-detail", "GetDetail");
  44. }
  45. [Fact]
  46. public void S8AppEndpoints_RequirePcMenuAndKeepSecondLayer()
  47. {
  48. AssertClassGuarded(typeof(AdoS8DashboardController), "/aidop/s8/dashboard");
  49. AssertClassGuarded(typeof(AdoS8ExceptionsController), "/aidop/s8/exceptions");
  50. AssertClassGuarded(typeof(AdoS8MasterDataController), "/aidop/s8/exceptions");
  51. AssertMethodsGuarded(typeof(AdoS8ConfigActionPermissionController),
  52. "/aidop/s8/exceptions", "MineAsync");
  53. Assert.NotNull(typeof(AdoS8DashboardController).GetMethod("OverviewAsync")!
  54. .GetCustomAttribute<S8PermissionAttribute>());
  55. Assert.NotNull(typeof(AdoS8ExceptionsController).GetMethod("ClaimAsync")!
  56. .GetCustomAttribute<S8ExceptionActionAttribute>());
  57. }
  58. [Fact]
  59. public void ApprovalTaskOwnershipGuard_RemainsInEngine()
  60. {
  61. var source = ReadRepoFile("server", "Plugins", "Admin.NET.Plugin.ApprovalFlow",
  62. "Service", "FlowEngine", "FlowEngineService.cs");
  63. Assert.Contains("task.AssigneeId != _userManager.UserId", source);
  64. Assert.Contains("当前用户不是该任务的审批人", source);
  65. }
  66. [Fact]
  67. public void AidopTenantHelper_DoesNotTrustRequestTenantId()
  68. {
  69. var source = ReadRepoFile("server", "Plugins", "Admin.NET.Plugin.AiDOP",
  70. "Infrastructure", "AidopTenantHelper.cs");
  71. Assert.Contains("AidopTenantScope.ResolveOrThrow", source);
  72. Assert.DoesNotContain("Request.Query", source);
  73. Assert.DoesNotContain("return requestTenantId", source);
  74. Assert.DoesNotContain("SqlSugarConst.DefaultTenantId", source);
  75. }
  76. private static void AssertClassGuarded(Type type, string expectedPath)
  77. {
  78. var paths = type.GetCustomAttributes<RequirePcMenuPathAttribute>(true)
  79. .Select(x => x.MenuPath);
  80. Assert.Contains(expectedPath, paths);
  81. }
  82. private static void AssertMethodsGuarded(Type type, string expectedPath, params string[] methodNames)
  83. {
  84. foreach (var methodName in methodNames)
  85. {
  86. var method = type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
  87. .SingleOrDefault(x => x.Name == methodName);
  88. Assert.NotNull(method);
  89. var paths = method!.GetCustomAttributes<RequirePcMenuPathAttribute>(true)
  90. .Select(x => x.MenuPath);
  91. Assert.Contains(expectedPath, paths);
  92. }
  93. }
  94. private static string ReadRepoFile(params string[] parts)
  95. {
  96. var dir = new DirectoryInfo(AppContext.BaseDirectory);
  97. while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "server")))
  98. dir = dir.Parent;
  99. Assert.NotNull(dir);
  100. return File.ReadAllText(Path.Combine(new[] { dir!.FullName }.Concat(parts).ToArray()));
  101. }
  102. }