using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform; /// /// 对接配置接口的租户收口契约。匿名写入与跨租户改登记必须在源码层被拿掉, /// 不靠运行时铺两套租户数据。 /// public class MdpConfigApiTenantIsolationTests { [Fact] public void ConfigWriteServices_AreNotAnonymous() { foreach (var file in new[] { "MdpSourceConfigService.cs", "MdpTenantStdSourceService.cs", "MdpSourceMappingService.cs", "MdpSyncEntityConfigService.cs", "MdpSyncTaskConfigService.cs" }) { var source = ReadPluginFile("DataPlatform", file); Assert.DoesNotContain("[AllowAnonymous]", source, StringComparison.Ordinal); } } [Fact] public void TenantStdSource_RejectsCrossTenantWrite() { var source = ReadPluginFile("DataPlatform", "MdpTenantStdSourceService.cs"); Assert.Contains("不能修改其他租户的来源登记", source, StringComparison.Ordinal); Assert.Contains("不能查看其他租户的来源登记", source, StringComparison.Ordinal); } [Fact] public void SourceList_IsTenantScoped_AndDoesNotReturnPassword() { var source = ReadPluginFile("DataPlatform", "MdpSourceConfigService.cs"); Assert.Contains("x.TenantId == _user.TenantId", source, StringComparison.Ordinal); Assert.DoesNotContain("dbPassword", source, StringComparison.Ordinal); Assert.DoesNotContain("DbPasswordEnc =", source.Split("public async Task GetList")[1].Split("public async Task Save")[0], StringComparison.Ordinal); } [Fact] public void FuncAndMenu_RegisterMappingPagesWithoutFuncInTitle() { var func = ReadRepoFile("Web", "src", "constants", "aidopFuncCodes.ts"); Assert.Contains("FUNC-S9-019", func, StringComparison.Ordinal); Assert.Contains("/aidop/data-platform/tenant-std-sources", func, StringComparison.Ordinal); Assert.Contains("FUNC-S9-020", func, StringComparison.Ordinal); Assert.Contains("/aidop/data-platform/source-mappings", func, StringComparison.Ordinal); var menu = ReadPluginFile("SeedData", "SysMenuSeedData.cs"); Assert.Contains("业务对象来源", menu, StringComparison.Ordinal); Assert.Contains("来源映射维护", menu, StringComparison.Ordinal); Assert.DoesNotContain("FUNC-S9-019", menu, StringComparison.Ordinal); Assert.DoesNotContain("FUNC-S9-020", menu, StringComparison.Ordinal); } private static string ReadPluginFile(string folder, string file) => ReadRepoFile("server", "Plugins", "Admin.NET.Plugin.AiDOP", folder, file); private static string ReadRepoFile(params string[] parts) { var dir = new DirectoryInfo(AppContext.BaseDirectory); while (dir != null) { var candidate = Path.Combine(new[] { dir.FullName }.Concat(parts).ToArray()); if (File.Exists(candidate)) return File.ReadAllText(candidate); dir = dir.Parent; } throw new FileNotFoundException(parts[^1]); } }