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));
}
}
}
///
/// approved_time 是 S5_L1_001、S7_L1_001 唯一的时间基准。2026-09 指标 SQL 改用它时没登记成必填列,
/// 于是整列为空也没人报错,S5/S7 全租户静默掉数。此断言锁住这条契约。
///
[Fact]
public void ApprovedTime_IsRequiredColumn()
{
Assert.Contains("approved_time", NeutralRequiredColumns.InvTrans);
Assert.Contains(NeutralRequiredColumns.For("INV_TRANS"), c => c.Name == "approved_time");
}
///
/// 每个写 mdp_std_inv_trans 的地方都必须在 Channels 里登记。
/// 漏登记的写入口不受必填列守卫约束:ASN 发运投影就是这么带着空 approved_time 上线的。
///
[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");
}
}