using System.Reflection;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Xunit;
namespace Admin.NET.Plugin.AiDOP.Tests.S0.Tenant;
///
/// S0-P0-TENANT-ID-CLOSURE 防回归契约。
///
/// 守住四条铁律,任一被破坏则 CI 直接失败:
/// 1. S0 业务 Controller 不得带类级 [AllowAnonymous](白名单外一律失败);
/// 2. 每个 S0 业务 action 必须显式解析租户(AdoS0TenantScope.TryResolveRequired);
/// 3. 不得只用 Domain / DomainCode 做隔离而没有任何 tenant 条件;
/// 4. 有 Create 的 Controller 必须显式 stamp 租户,不得依赖全局 AOP。
///
/// 用源码静态扫描而非反射执行,避免测试需要真实 DB / HTTP 上下文。
///
public class S0ControllerTenantClosureTests
{
///
/// 允许匿名的 S0 接口白名单(必须写明业务理由)。
/// 当前为空:S0 全部业务接口都要求认证。
/// 新增条目前必须在评审中给出明确业务理由,否则本测试即为拦截点。
///
private static readonly HashSet IntentionalAnonymousWhitelist = new(StringComparer.Ordinal)
{
// 例:("AdoS0XxxController", "对外健康探活,不返回任何业务数据")
};
///
/// 无数据库访问、因而不需要租户解析的 action 白名单(按 Controller 文件名.Action 精确登记)。
/// 仅允许纯静态字典 / 静态模板;任何触达仓储的 action 都不得登记在此。
///
private static readonly HashSet NoDataAccessActions = new(StringComparer.Ordinal)
{
"AdoS0QualityDictionaryController.GetNodes", // 写死的前端菜单节点数组
"AdoS0EmpWorkDutiesController.GetDutyOptions", // 代码内静态 DutyMap 枚举
"AdoS0QmsSpecImportController.RawTemplate", // 静态 Excel 导入模板
"AdoS0QmsSpecImportController.ProcessTemplate", // 静态 Excel 导入模板
"AdoS0QmsSpecImportController.FqcTemplate", // 静态 Excel 导入模板
"AdoS0QmsSpecImportController.OqcTemplate", // 静态 Excel 导入模板
};
private static string ControllersRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null && !Directory.Exists(Path.Combine(dir.FullName, "Admin.NET.Plugin.AiDOP", "Controllers", "S0")))
dir = dir.Parent;
Assert.NotNull(dir);
return Path.Combine(dir!.FullName, "Admin.NET.Plugin.AiDOP", "Controllers", "S0");
}
public static IEnumerable