using Admin.NET.Plugin.AiDOP.Entity.S8; using Admin.NET.Plugin.AiDOP.Service.S8; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.S8; /// /// S8-STEP6C-CFG-NOTIFY-FIX-AND-CERT-1 · N-1 回归。 /// /// 覆盖 的逐层覆盖语义。这是纯函数测试,不触 DB、不触 pusher。 /// runtime resolver 与配置页 ListAsync 都调用同一个 Effective(),因此本文件同时守住两处。 /// /// 被守住的缺陷(修正前真实存在):工厂只为某一层建行,平台默认在该 scene+severity 下的 /// **其余层级会整体消失** —— 工厂本意「补一层」,实际「换掉整组」,且页面上不可见。 /// public class S8NotificationLayerMergeTests { private const long GlobalTenant = 0L; private const long GlobalFactory = 0L; private const long FactoryTenant = 838257186181189L; private const long FactoryFactory = 838257186320453L; private static AdoS8NotificationLayer Row( long id, long tenantId, long factoryId, string scene, string severity, string level) => new() { Id = id, TenantId = tenantId, FactoryId = factoryId, SceneCode = scene, Severity = severity, LevelCode = level, TargetRoleIds = "ROLE_X", NotifyChannel = "log,SignalR" }; private static AdoS8NotificationLayer Global(long id, string scene, string sev, string level) => Row(id, GlobalTenant, GlobalFactory, scene, sev, level); private static AdoS8NotificationLayer Factory(long id, string scene, string sev, string level) => Row(id, FactoryTenant, FactoryFactory, scene, sev, level); /// /// N-1 的核心回归:工厂只覆盖 L2,平台默认的 L1 / L3 必须**继续生效**。 /// 修正前此处会只剩 1 行(工厂的 L2),L1/L3 静默消失。 /// [Fact] public void FactoryOverrideOnOneLevel_KeepsPlatformDefaultsOnOtherLevels() { var candidates = new[] { Global(1, "S1", "FOLLOW", "L1_OPERATOR"), Global(2, "S1", "FOLLOW", "L2_MANAGER"), Global(3, "S1", "FOLLOW", "L3_DIRECTOR"), Factory(100, "S1", "FOLLOW", "L2_MANAGER"), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Equal(3, effective.Count); Assert.Equal(new[] { "L1_OPERATOR", "L2_MANAGER", "L3_DIRECTOR" }, effective.Select(x => x.LevelCode)); // L1 / L3 仍来自平台默认,L2 被工厂接管 Assert.Equal(GlobalTenant, effective[0].TenantId); Assert.Equal(FactoryTenant, effective[1].TenantId); Assert.Equal(100, effective[1].Id); Assert.Equal(GlobalTenant, effective[2].TenantId); } /// 同层级有工厂行时,该层级的平台默认必须被替换掉(不能两行都发)。 [Fact] public void FactoryRow_ReplacesPlatformDefault_OnSameLevel() { var candidates = new[] { Global(1, "S1", "FOLLOW", "L1_OPERATOR"), Factory(100, "S1", "FOLLOW", "L1_OPERATOR"), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Single(effective); Assert.Equal(100, effective[0].Id); } /// 覆盖不跨 scene / severity:S1 的工厂行不得影响 S2,FOLLOW 的不得影响 SERIOUS。 [Fact] public void Override_DoesNotLeakAcrossSceneOrSeverity() { var candidates = new[] { Global(1, "S1", "FOLLOW", "L1_OPERATOR"), Global(2, "S1", "SERIOUS", "L1_OPERATOR"), Global(3, "S2", "FOLLOW", "L1_OPERATOR"), Factory(100, "S1", "FOLLOW", "L1_OPERATOR"), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Equal(3, effective.Count); Assert.Equal(100, effective.Single(x => x.SceneCode == "S1" && x.Severity == "FOLLOW").Id); Assert.Equal(2, effective.Single(x => x.SceneCode == "S1" && x.Severity == "SERIOUS").Id); Assert.Equal(3, effective.Single(x => x.SceneCode == "S2").Id); } /// /// severity 参与分组前必须归一:平台默认写 FOLLOW、工厂行写 legacy 的 LOW, /// 二者是同一档,必须归到同一组由工厂接管,而不是各发一条。 /// [Fact] public void SeverityIsNormalizedBeforeGrouping() { var candidates = new[] { Global(1, "S1", "FOLLOW", "L1_OPERATOR"), Factory(100, "S1", "LOW", "L1_OPERATOR"), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Single(effective); Assert.Equal(100, effective[0].Id); } /// level_code 归一:大小写 / 空白差异不得让工厂行与平台默认分到两组。 [Fact] public void LevelIsNormalizedBeforeGrouping() { var candidates = new[] { Global(1, "S1", "FOLLOW", "L1_OPERATOR"), Factory(100, "S1", "FOLLOW", " l1_operator "), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Single(effective); Assert.Equal(100, effective[0].Id); } /// /// 历史脏数据:level_code = '' 的行自成一组,既不吞掉合法层级、也不被合法层级吞掉。 /// (库中实测存在这样的行。) /// [Fact] public void EmptyLevelCode_FormsItsOwnGroup() { var candidates = new[] { Global(1, "S1", "SERIOUS", "L1_OPERATOR"), Factory(100, "S1", "SERIOUS", ""), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Equal(2, effective.Count); Assert.Contains(effective, x => x.Id == 1); Assert.Contains(effective, x => x.Id == 100); } /// /// 无唯一索引,同键可能重复行:必须全部保留(丢弃会让 runtime 与页面不一致), /// 且顺序按 Id 稳定,不依赖 DB 返回序。 /// [Fact] public void DuplicateRowsOnSameKey_AreAllKept_InStableIdOrder() { var candidates = new[] { Factory(300, "S1", "FOLLOW", "L1_OPERATOR"), Factory(100, "S1", "FOLLOW", "L1_OPERATOR"), Global(1, "S1", "FOLLOW", "L1_OPERATOR"), }; var effective = S8NotificationLayerMerge.Effective(candidates); Assert.Equal(new[] { 100L, 300L }, effective.Select(x => x.Id)); } /// 输入顺序不影响输出:稳定确定序是 scene → severity → level → id。 [Fact] public void OrderingIsDeterministic_RegardlessOfInputOrder() { var rows = new[] { Global(3, "S2", "FOLLOW", "L1_OPERATOR"), Global(2, "S1", "SERIOUS", "L1_OPERATOR"), Global(1, "S1", "FOLLOW", "L2_MANAGER"), Global(4, "S1", "FOLLOW", "L1_OPERATOR"), }; var forward = S8NotificationLayerMerge.Effective(rows).Select(x => x.Id).ToList(); var reversed = S8NotificationLayerMerge.Effective(rows.Reverse().ToList()).Select(x => x.Id).ToList(); Assert.Equal(new[] { 4L, 1L, 2L, 3L }, forward); Assert.Equal(forward, reversed); } /// 只有平台默认时全部生效;只有工厂行时同理。 [Fact] public void SingleSidedCandidates_PassThrough() { var globalsOnly = S8NotificationLayerMerge.Effective(new[] { Global(1, "S1", "FOLLOW", "L1_OPERATOR"), Global(2, "S1", "FOLLOW", "L2_MANAGER"), }); Assert.Equal(2, globalsOnly.Count); var factoryOnly = S8NotificationLayerMerge.Effective(new[] { Factory(100, "S1", "FOLLOW", "L1_OPERATOR"), }); Assert.Single(factoryOnly); Assert.Empty(S8NotificationLayerMerge.Effective(Array.Empty())); } }