| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475 |
- using Admin.NET.Plugin.AiDOP.DataPlatform;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.DataPlatform;
- public class NeutralRequiredColumnsGuardTests
- {
- [Fact]
- public void EveryRequiredColumn_IsProjectedOrDeclaredMissing()
- {
- var root = RepoRoot();
- var plugin = Path.Combine(root, "server", "Plugins", "Admin.NET.Plugin.AiDOP");
- foreach (var channel in NeutralRequiredColumns.Channels)
- {
- var path = Path.Combine(plugin, channel.RelativePath);
- Assert.True(File.Exists(path), channel.Channel + " 缺少 " + path);
- var text = File.ReadAllText(path);
- var start = text.IndexOf(channel.Marker, StringComparison.Ordinal);
- Assert.True(start >= 0, channel.Channel + " 找不到 " + channel.Marker);
- var body = text[start..];
- foreach (var column in channel.Present)
- Assert.Contains(column, body, StringComparison.Ordinal);
- var covered = channel.Present.Concat(channel.Missing).ToHashSet(StringComparer.Ordinal);
- if (channel.Channel != "NATIVE")
- {
- var missing = NeutralRequiredColumns.InvTrans.Where(c => !covered.Contains(c)).ToList();
- Assert.True(missing.Count == 0, channel.Channel + " 未覆盖: " + string.Join(",", missing));
- }
- }
- }
- /// <summary>
- /// approved_time 是 S5_L1_001、S7_L1_001 唯一的时间基准。2026-09 指标 SQL 改用它时没登记成必填列,
- /// 于是整列为空也没人报错,S5/S7 全租户静默掉数。此断言锁住这条契约。
- /// </summary>
- [Fact]
- public void ApprovedTime_IsRequiredColumn()
- {
- Assert.Contains("approved_time", NeutralRequiredColumns.InvTrans);
- Assert.Contains(NeutralRequiredColumns.For("INV_TRANS"), c => c.Name == "approved_time");
- }
- /// <summary>
- /// 每个写 mdp_std_inv_trans 的地方都必须在 Channels 里登记。
- /// 漏登记的写入口不受必填列守卫约束:ASN 发运投影就是这么带着空 approved_time 上线的。
- /// </summary>
- [Fact]
- public void EveryInvTransWriter_IsRegisteredAsChannel()
- {
- var plugin = Path.Combine(RepoRoot(), "server", "Plugins", "Admin.NET.Plugin.AiDOP");
- var registered = NeutralRequiredColumns.Channels
- .Select(c => Path.Combine(plugin, c.RelativePath))
- .ToHashSet(StringComparer.OrdinalIgnoreCase);
- var unregistered = Directory
- .EnumerateFiles(plugin, "*.cs", SearchOption.AllDirectories)
- .Where(f => File.ReadAllText(f).Contains("INSERT INTO mdp_std_inv_trans", StringComparison.Ordinal))
- .Where(f => !registered.Contains(f))
- .ToList();
- Assert.True(unregistered.Count == 0, "未登记为必填列通道: " + string.Join(", ", unregistered));
- }
- 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");
- }
- }
|