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()));
}
}