using System.Reflection;
using Admin.NET.Core;
using Admin.NET.Plugin.AiDOP.Controllers;
using Admin.NET.Plugin.AiDOP.Controllers.S8;
using Admin.NET.Plugin.AiDOP.FinishedWarehouse;
using Admin.NET.Plugin.AiDOP.Infrastructure;
using Admin.NET.Plugin.AiDOP.Infrastructure.S8;
using Admin.NET.Plugin.AiDOP.MaterialWarehouse;
using Admin.NET.Plugin.AiDOP.Order;
using Admin.NET.Plugin.AiDOP.Production;
using Admin.NET.Plugin.ApprovalFlow.Service;
using Xunit;
namespace Admin.NET.Plugin.AiDOP.Tests.Security;
/// App 业务 API 必须复用 PC 菜单 path 权限,且保留既有业务内层守卫。
public class AppApiPcMenuGuardTests
{
private const string ApprovalCenter = "/aidop/flowManage/approvalFlowCenter";
[Fact]
public void ApprovalAppEndpoints_RequireApprovalCenterMenu()
{
AssertMethodsGuarded(typeof(FlowTaskService), ApprovalCenter,
"MyPendingPage", "MyDonePage", "MyInitiatedPage", "MyPendingCount",
"Approve", "Reject", "Transfer", "Withdraw", "ReturnToPrev", "AddSign",
"Urge", "BatchApprove", "BatchReject");
AssertMethodsGuarded(typeof(FlowInstanceService), ApprovalCenter, "GetDetail", "GetTimeline");
AssertMethodsGuarded(typeof(ApprovalFlowService), ApprovalCenter, "GetBizTypes");
}
[Fact]
public void AidopQueryEndpoints_RequireCorrespondingPcMenus()
{
Assert.NotNull(typeof(AidopKanbanController).GetMethod("GetDashboardPage")!
.GetCustomAttribute());
AssertMethodsGuarded(typeof(AidopKanbanController), "/aidop/smart-ops/s1",
"GetSmartOpsFilterOptions");
AssertMethodsGuarded(typeof(StockQueryService), "/aidop/s5/inventory/stock-query",
"GetList", "GetTypeOptions", "GetLocationOptions");
AssertMethodsGuarded(typeof(WorkOrderProgressDashboardService),
"/aidop/s2/collaboration-kanban/work-order-progress", "GetList");
AssertMethodsGuarded(typeof(OrderDeliveryService), "/aidop/s1/order-mgmt/orderDelivery",
"GetDeliveryList", "GetDeliveryFlow");
AssertMethodsGuarded(typeof(FqcInspBillService), "/aidop/s7/fqc/task-list", "GetTaskList");
AssertMethodsGuarded(typeof(FqcInspBillService), "/aidop/s7/fqc/result-list", "GetResultList");
AssertMethodsGuarded(typeof(FqcInspBillService), "/aidop/s7/fqc/result-detail", "GetDetail");
}
[Fact]
public void S8AppEndpoints_RequirePcMenuAndKeepSecondLayer()
{
AssertClassGuarded(typeof(AdoS8DashboardController), "/aidop/s8/dashboard");
AssertClassGuarded(typeof(AdoS8ExceptionsController), "/aidop/s8/exceptions");
AssertClassGuarded(typeof(AdoS8MasterDataController), "/aidop/s8/exceptions");
AssertMethodsGuarded(typeof(AdoS8ConfigActionPermissionController),
"/aidop/s8/exceptions", "MineAsync");
Assert.NotNull(typeof(AdoS8DashboardController).GetMethod("OverviewAsync")!
.GetCustomAttribute());
Assert.NotNull(typeof(AdoS8ExceptionsController).GetMethod("ClaimAsync")!
.GetCustomAttribute());
}
[Fact]
public void ApprovalTaskOwnershipGuard_RemainsInEngine()
{
var source = ReadRepoFile("server", "Plugins", "Admin.NET.Plugin.ApprovalFlow",
"Service", "FlowEngine", "FlowEngineService.cs");
Assert.Contains("task.AssigneeId != _userManager.UserId", source);
Assert.Contains("当前用户不是该任务的审批人", source);
}
[Fact]
public void AidopTenantHelper_DoesNotTrustRequestTenantId()
{
var source = ReadRepoFile("server", "Plugins", "Admin.NET.Plugin.AiDOP",
"Infrastructure", "AidopTenantHelper.cs");
Assert.Contains("AidopTenantScope.ResolveOrThrow", source);
Assert.DoesNotContain("Request.Query", source);
Assert.DoesNotContain("return requestTenantId", source);
Assert.DoesNotContain("SqlSugarConst.DefaultTenantId", source);
}
private static void AssertClassGuarded(Type type, string expectedPath)
{
var paths = type.GetCustomAttributes(true)
.Select(x => x.MenuPath);
Assert.Contains(expectedPath, paths);
}
private static void AssertMethodsGuarded(Type type, string expectedPath, params string[] methodNames)
{
foreach (var methodName in methodNames)
{
var method = type.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.SingleOrDefault(x => x.Name == methodName);
Assert.NotNull(method);
var paths = method!.GetCustomAttributes(true)
.Select(x => x.MenuPath);
Assert.Contains(expectedPath, paths);
}
}
private static string ReadRepoFile(params string[] parts)
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "server")))
dir = dir.Parent;
Assert.NotNull(dir);
return File.ReadAllText(Path.Combine(new[] { dir!.FullName }.Concat(parts).ToArray()));
}
}