using System.Reflection; using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Service.S8.Rules; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.S8; /// /// S8-STANDARD-DATASET-HARD-CUTOVER-1:Legacy 符号**不存在**契约。 /// /// 与上一阶段的区别必须说清楚,否则这个文件会被误当成重复覆盖: /// · 退役阶段断言的是「Legacy 被拒绝」——类型还在,只是调用会抛 410 / legacy_sql_retired; /// · 本阶段断言的是「Legacy 根本不存在」——类型、Provider、参数、表全部消失。 /// 前者的测试已随实现一并删除;保留它们只会让人以为 Legacy 仍是一条需要防守的路径。 /// /// 用**运行期反射按名字查找**而非编译期引用:已删除的类型写不进 typeof(), /// 编译期引用等于永远无法表达「它不存在」这个断言。 /// 名字用完全限定名,避免同名新类型意外让断言变绿。 /// public class S8LegacySymbolsRemovedTests { private static readonly Assembly PluginAssembly = typeof(AdoS8WatchRule).Assembly; private static Type? Find(string fullName) => PluginAssembly.GetType(fullName, throwOnError: false); /// /// 全量 Legacy 类型清单。任何一条复活都会让本用例红—— /// 这正是目的:Hard Cutover 之后不接受「先加回来再说」。 /// public static TheoryData RemovedTypes() => new() { // 报警规则(历史只读壳,Runtime Authority 一直是 AdoS8WatchRule) "Admin.NET.Plugin.AiDOP.Entity.S8.AdoS8AlertRule", "Admin.NET.Plugin.AiDOP.Service.S8.S8AlertRuleService", "Admin.NET.Plugin.AiDOP.Controllers.S8.AdoS8ConfigAlertRulesController", }; [Theory] [MemberData(nameof(RemovedTypes))] public void LegacyType_NoLongerExists(string fullName) { Assert.Null(Find(fullName)); } /// /// T10:evaluator 契约不得再出现 alertRules 死参数。 /// /// 该形参存在期间,三个实现的方法体读取次数均为 0,调度器查库后转手丢弃—— /// 一个纯粹的假依赖,却让每个 tick 多打一次全表查询,也误导读者以为告警阈值来自那张表。 /// [Fact] public void EvaluatorContract_HasNoAlertRulesParameter() { var evaluate = typeof(IS8RuleEvaluator).GetMethod(nameof(IS8RuleEvaluator.EvaluateAsync))!; var names = evaluate.GetParameters().Select(p => p.Name).ToArray(); Assert.DoesNotContain("alertRules", names); Assert.Equal(new[] { "tenantId", "factoryId", "rule", "cancellationToken" }, names); } /// /// 所有实现必须与接口同步收窄。只查接口不够—— /// 实现类若保留旧重载,调用方仍可能绑到它。 /// [Fact] public void AllEvaluatorImplementations_HaveNoAlertRulesParameter() { var implementations = PluginAssembly.GetTypes() .Where(t => t is { IsClass: true, IsAbstract: false } && typeof(IS8RuleEvaluator).IsAssignableFrom(t)) .ToArray(); // 防止「一个实现都没找到」时本用例空转通过。 Assert.NotEmpty(implementations); foreach (var impl in implementations) { var overloads = impl.GetMethods(BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name == nameof(IS8RuleEvaluator.EvaluateAsync)) .ToArray(); Assert.NotEmpty(overloads); foreach (var m in overloads) Assert.DoesNotContain(m.GetParameters(), p => p.Name == "alertRules"); } } }