S8LegacySymbolsRemovedTests.cs 3.7 KB

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