using System.Text.RegularExpressions;
using Admin.NET.Plugin.AiDOP.DataPlatform;
using Xunit;
namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform;
///
/// 目录必须盖住代码里声明的中立表,以及当前库已有、但 DDL 不在 MdpSchemaDefinition 的表。
/// 投影文件里的 INSERT INTO mdp_std_ 必须经过来源闸门。
///
public class MdpStdObjectCatalogGuardTests
{
private static readonly string[] PhysicalTablesOutsideSchema =
[
"mdp_std_action_event",
"mdp_std_delivery_exception",
"mdp_std_delivery_result",
"mdp_std_delivery_schedule",
"mdp_std_employee",
"mdp_std_inv_trans",
"mdp_std_inventory",
"mdp_std_inventory_balance_monthly",
"mdp_std_iqc_inspection",
"mdp_std_item",
"mdp_std_material_readiness",
"mdp_std_process_outsource_order",
"mdp_std_purchase_order",
"mdp_std_purchase_order_completion",
"mdp_std_purchase_request",
"mdp_std_ship_trans",
"mdp_std_so",
"mdp_std_supplier",
"mdp_std_supplier_item",
"mdp_std_supply_demand",
"mdp_std_work_order_bom",
"mdp_std_work_order_line",
];
[Fact]
public void Catalog_CoversEveryNeutralTable()
{
var schema = ReadRepoFile(Path.Combine(
"server", "Plugins", "Admin.NET.Plugin.AiDOP", "DataPlatform", "Schema", "MdpSchemaDefinition.cs"));
var names = Regex.Matches(schema, "\\[\"(mdp_std_[a-z0-9_]+)\"\\]")
.Select(m => m.Groups[1].Value)
.Concat(PhysicalTablesOutsideSchema)
.Where(n => !n.StartsWith("mdp_std_t8_", StringComparison.OrdinalIgnoreCase)
&& !n.Contains("_unmapped", StringComparison.OrdinalIgnoreCase)
&& !n.Contains("_bak_", StringComparison.OrdinalIgnoreCase))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
var covered = MdpStdObjectCatalog.All.SelectMany(d => d.Tables).ToHashSet(StringComparer.OrdinalIgnoreCase);
var missing = names.Where(n => !covered.Contains(n)).OrderBy(n => n).ToList();
Assert.True(missing.Count == 0, "目录未覆盖: " + string.Join(", ", missing));
}
[Fact]
public void NeutralInserts_CallSourceGate()
{
var root = RepoRoot();
var plugin = Path.Combine(root, "server", "Plugins", "Admin.NET.Plugin.AiDOP");
var hits = new List();
foreach (var file in Directory.EnumerateFiles(plugin, "*.cs", SearchOption.AllDirectories))
{
if (file.Contains($"{Path.DirectorySeparatorChar}Tests{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
continue;
var text = File.ReadAllText(file);
var targets = Regex.Matches(text, @"INSERT\s+INTO\s+(mdp_std_[A-Za-z0-9_]+)", RegexOptions.IgnoreCase)
.Select(m => m.Groups[1].Value)
.Where(n => !n.Contains("unmapped", StringComparison.OrdinalIgnoreCase)
&& !n.Contains("_bak_", StringComparison.OrdinalIgnoreCase))
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (targets.Count == 0)
continue;
if (!text.Contains("MdpNeutralSourceGate", StringComparison.Ordinal)
&& !text.Contains("MdpTenantStdSourceResolver", StringComparison.Ordinal))
hits.Add(Path.GetRelativePath(plugin, file));
}
Assert.True(hits.Count == 0, "中立层写入未调用来源闸门: " + string.Join(", ", hits));
}
private static string ReadRepoFile(string relative)
{
var path = Path.Combine(RepoRoot(), relative);
if (!File.Exists(path)) throw new FileNotFoundException(path);
return File.ReadAllText(path);
}
private static string RepoRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir != null)
{
if (Directory.Exists(Path.Combine(dir.FullName, "server", "Plugins", "Admin.NET.Plugin.AiDOP")))
return dir.FullName;
dir = dir.Parent;
}
throw new DirectoryNotFoundException("repo root");
}
}