using System.Text.RegularExpressions;
using Xunit;
namespace BuildFlow.Architecture.Tests;
///
/// 权限码注册约束测试。保证 Host 端点声明的权限码都能进入平台权限表,
/// 避免端点要求了权限但角色管理页无法分配,或登录 token 永远拿不到对应权限码。
///
public partial class PermissionRegistrationTests
{
[Fact]
public void EndpointPermissionCodesShouldBeSeeded()
{
var repoRoot = FindRepoRoot();
var endpointCodes = ReadEndpointPermissionCodes(repoRoot).ToArray();
var seededCodes = ReadSeedPermissionCodes(repoRoot).ToHashSet(StringComparer.Ordinal);
var missing = endpointCodes
.Where(code => !seededCodes.Contains(code))
.ToArray();
Assert.True(
missing.Length == 0,
"端点声明的权限码必须同步写入 SeedData.SeedPermissionsAsync,否则角色无法分配这些权限:" +
Environment.NewLine +
string.Join(Environment.NewLine, missing));
}
[Fact]
public void SeededPlatformPermissionCodesShouldBeEnforcedByEndpoints()
{
var repoRoot = FindRepoRoot();
var endpointCodes = ReadEndpointPermissionCodes(repoRoot).ToHashSet(StringComparer.Ordinal);
var seededCodes = ReadSeedPermissionCodes(repoRoot)
// button PermissionCode 允许配置为业务动态权限,不一定对应固定 endpoint。
.Where(code => !code.StartsWith("dynamic:", StringComparison.Ordinal))
.Order(StringComparer.Ordinal)
.ToArray();
var unused = seededCodes
.Where(code => !endpointCodes.Contains(code))
.ToArray();
Assert.True(
unused.Length == 0,
"SeedData 中的平台权限码必须有对应后端端点强校验,否则角色管理页可分配但后端不生效:" +
Environment.NewLine +
string.Join(Environment.NewLine, unused));
}
private static DirectoryInfo FindRepoRoot()
{
var current = new DirectoryInfo(AppContext.BaseDirectory);
while (current is not null)
{
if (File.Exists(Path.Combine(current.FullName, "BuildFlow.slnx")))
{
return current;
}
current = current.Parent;
}
throw new InvalidOperationException("无法定位 BuildFlow.slnx,权限码注册测试无法确定仓库边界。");
}
private static bool ContainsPathSegment(string path, string segment)
{
return path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
.Any(part => string.Equals(part, segment, StringComparison.Ordinal));
}
private static IEnumerable ReadEndpointPermissionCodes(DirectoryInfo repoRoot)
{
var endpointRoot = Path.Combine(repoRoot.FullName, "src", "BuildFlow.Host", "Endpoints");
return Directory
.EnumerateFiles(endpointRoot, "*.cs", SearchOption.AllDirectories)
.Where(path => !ContainsPathSegment(path, "bin") && !ContainsPathSegment(path, "obj"))
.SelectMany(path => PermissionCallRegex()
.Matches(File.ReadAllText(path))
.Select(match => match.Groups["code"].Value))
.Distinct(StringComparer.Ordinal)
.Order(StringComparer.Ordinal);
}
private static IEnumerable ReadSeedPermissionCodes(DirectoryInfo repoRoot)
{
var seedFile = Path.Combine(
repoRoot.FullName,
"src",
"modules",
"platform",
"Infrastructure",
"Persistence",
"SeedData.cs");
return SeedPermissionRegex()
.Matches(File.ReadAllText(seedFile))
.Select(match => match.Groups["code"].Value)
.Distinct(StringComparer.Ordinal)
.Order(StringComparer.Ordinal);
}
[GeneratedRegex("Permissions\\(\"(?[^\"]+)\"\\)")]
private static partial Regex PermissionCallRegex();
[GeneratedRegex("new\\s+SeedPermission\\([^,]+,\\s*\"[^\"]+\",\\s*\"(?[^\"]+)\"")]
private static partial Regex SeedPermissionRegex();
}