MdpStdObjectCatalogGuardTests.cs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. using System.Text.RegularExpressions;
  2. using Admin.NET.Plugin.AiDOP.DataPlatform;
  3. using Xunit;
  4. namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform;
  5. /// <summary>
  6. /// 目录必须盖住代码里声明的中立表,以及当前库已有、但 DDL 不在 MdpSchemaDefinition 的表。
  7. /// 投影文件里的 INSERT INTO mdp_std_ 必须经过来源闸门。
  8. /// </summary>
  9. public class MdpStdObjectCatalogGuardTests
  10. {
  11. private static readonly string[] PhysicalTablesOutsideSchema =
  12. [
  13. "mdp_std_action_event",
  14. "mdp_std_delivery_exception",
  15. "mdp_std_delivery_result",
  16. "mdp_std_delivery_schedule",
  17. "mdp_std_employee",
  18. "mdp_std_inv_trans",
  19. "mdp_std_inventory",
  20. "mdp_std_inventory_balance_monthly",
  21. "mdp_std_iqc_inspection",
  22. "mdp_std_item",
  23. "mdp_std_material_readiness",
  24. "mdp_std_process_outsource_order",
  25. "mdp_std_purchase_order",
  26. "mdp_std_purchase_order_completion",
  27. "mdp_std_purchase_request",
  28. "mdp_std_ship_trans",
  29. "mdp_std_so",
  30. "mdp_std_supplier",
  31. "mdp_std_supplier_item",
  32. "mdp_std_supply_demand",
  33. "mdp_std_work_order_bom",
  34. "mdp_std_work_order_line",
  35. ];
  36. [Fact]
  37. public void Catalog_CoversEveryNeutralTable()
  38. {
  39. var schema = ReadRepoFile(Path.Combine(
  40. "server", "Plugins", "Admin.NET.Plugin.AiDOP", "DataPlatform", "Schema", "MdpSchemaDefinition.cs"));
  41. var names = Regex.Matches(schema, "\\[\"(mdp_std_[a-z0-9_]+)\"\\]")
  42. .Select(m => m.Groups[1].Value)
  43. .Concat(PhysicalTablesOutsideSchema)
  44. .Where(n => !n.StartsWith("mdp_std_t8_", StringComparison.OrdinalIgnoreCase)
  45. && !n.Contains("_unmapped", StringComparison.OrdinalIgnoreCase)
  46. && !n.Contains("_bak_", StringComparison.OrdinalIgnoreCase))
  47. .Distinct(StringComparer.OrdinalIgnoreCase)
  48. .ToList();
  49. var covered = MdpStdObjectCatalog.All.SelectMany(d => d.Tables).ToHashSet(StringComparer.OrdinalIgnoreCase);
  50. var missing = names.Where(n => !covered.Contains(n)).OrderBy(n => n).ToList();
  51. Assert.True(missing.Count == 0, "目录未覆盖: " + string.Join(", ", missing));
  52. }
  53. [Fact]
  54. public void NeutralInserts_CallSourceGate()
  55. {
  56. var root = RepoRoot();
  57. var plugin = Path.Combine(root, "server", "Plugins", "Admin.NET.Plugin.AiDOP");
  58. var hits = new List<string>();
  59. foreach (var file in Directory.EnumerateFiles(plugin, "*.cs", SearchOption.AllDirectories))
  60. {
  61. if (file.Contains($"{Path.DirectorySeparatorChar}Tests{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase))
  62. continue;
  63. var text = File.ReadAllText(file);
  64. var targets = Regex.Matches(text, @"INSERT\s+INTO\s+(mdp_std_[A-Za-z0-9_]+)", RegexOptions.IgnoreCase)
  65. .Select(m => m.Groups[1].Value)
  66. .Where(n => !n.Contains("unmapped", StringComparison.OrdinalIgnoreCase)
  67. && !n.Contains("_bak_", StringComparison.OrdinalIgnoreCase))
  68. .Distinct(StringComparer.OrdinalIgnoreCase)
  69. .ToList();
  70. if (targets.Count == 0)
  71. continue;
  72. if (!text.Contains("MdpNeutralSourceGate", StringComparison.Ordinal)
  73. && !text.Contains("MdpTenantStdSourceResolver", StringComparison.Ordinal))
  74. hits.Add(Path.GetRelativePath(plugin, file));
  75. }
  76. Assert.True(hits.Count == 0, "中立层写入未调用来源闸门: " + string.Join(", ", hits));
  77. }
  78. private static string ReadRepoFile(string relative)
  79. {
  80. var path = Path.Combine(RepoRoot(), relative);
  81. if (!File.Exists(path)) throw new FileNotFoundException(path);
  82. return File.ReadAllText(path);
  83. }
  84. private static string RepoRoot()
  85. {
  86. var dir = new DirectoryInfo(AppContext.BaseDirectory);
  87. while (dir != null)
  88. {
  89. if (Directory.Exists(Path.Combine(dir.FullName, "server", "Plugins", "Admin.NET.Plugin.AiDOP")))
  90. return dir.FullName;
  91. dir = dir.Parent;
  92. }
  93. throw new DirectoryNotFoundException("repo root");
  94. }
  95. }