| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- using Admin.NET.Plugin.AiDOP.Entity.S8;
- using Admin.NET.Plugin.AiDOP.Service.S8;
- using Xunit;
- namespace Admin.NET.Test.S8;
- /// <summary>
- /// S8-CONFIG-AUTHORITY-FIX-1:结果 KPI 的「停用即不显示」门禁。
- ///
- /// 修复前的缺陷:<c>GetResultKpiSummaryAsync</c> 只按 mechanism / is_result_kpi / 租户作用域取行,
- /// <b>完全不读 enabled</b>。运行库里 5 条结果 KPI 全部 enabled=0(remark "[RESERVED] S9 result KPI"),
- /// 却照常出现在监控总览——配置页面的停用开关对业务零效力。
- ///
- /// 本文件锁定的契约与失败后果:
- /// A. enabled=true → 纳入 → 否则正常指标消失,属功能倒退
- /// B. enabled=false → 不纳入 → 否则"停用"仍然显示,用户认为配置坏了
- /// C. is_result_kpi=false → 不纳入 → 否则机制字典项(Rule 向导用)会污染结果 KPI 卡片
- /// D. 门禁必须在**覆盖解析之后** → 这是最容易写错的一条:
- /// 若把 Enabled 下推成 SQL WHERE,租户 override enabled=false 的行会先被滤掉,
- /// 分组里只剩 enabled=true 的全局行 → 结果回落成"显示",与停用意图完全相反。
- /// E. 不触碰 Rule 机制字典 → 门禁只作用于结果 KPI 路径
- ///
- /// 不接 DB、不接 DI、不触发任何真实规则。
- /// </summary>
- public class S8ResultKpiEnabledGateTests
- {
- private const long TenantId = 838257186181189L;
- private const long FactoryId = 838257186320453L;
- private static AdoS8MonitorMetric Metric(
- string code, bool enabled, bool isResultKpi = true,
- long tenantId = 0, long factoryId = 0, string mechanism = "RATIO", int sortNo = 0) => new()
- {
- MetricCode = code,
- MetricName = code,
- Mechanism = mechanism,
- IsResultKpi = isResultKpi,
- Enabled = enabled,
- TenantId = tenantId,
- FactoryId = factoryId,
- SortNo = sortNo
- };
- private static List<string> CodesOf(IEnumerable<AdoS8MonitorMetric> rows) =>
- rows.Select(x => x.MetricCode).ToList();
- // ============================================================
- // A/B/C. 基础门禁
- // ============================================================
- [Fact]
- public void T1_EnabledResultKpi_IsReturned()
- {
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("ORDER_DELIVERY_RATE", enabled: true)
- });
- Assert.Equal(new[] { "ORDER_DELIVERY_RATE" }, CodesOf(result));
- }
- [Fact]
- public void T2_DisabledResultKpi_IsNotReturned()
- {
- // 运行库现状:5 条结果 KPI 全部 enabled=0,修复前它们照常显示。
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("IQC_PASS_RATE", enabled: false)
- });
- Assert.Empty(result);
- }
- [Fact]
- public void T3_NonResultKpi_IsNotReturned()
- {
- // is_result_kpi=false 的是 Rule 向导用的机制字典项(DATE / VALUE_RANGE),
- // 即使 enabled=true 也不属于结果 KPI 卡片。
- // 注:调用方 SQL 已按 IsResultKpi 过滤,这里再断言一次,防止将来有人把过滤挪进本方法时写反。
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("ORDER_DUE_AT", enabled: true, isResultKpi: false, mechanism: "DATE")
- }.Where(m => m.IsResultKpi));
- Assert.Empty(result);
- }
- // ============================================================
- // D. 覆盖解析与门禁的先后顺序 —— 本文件最关键的两条
- // ============================================================
- [Fact]
- public void T4_TenantOverrideDisable_HidesMetric_EvenWhenGlobalEnabled()
- {
- // 全局启用 + 租户停用 → 租户看不到。
- // 若实现把 Enabled 下推成 SQL WHERE,租户行会先被滤掉、只剩全局行,
- // 结果反而变成"可见"——本用例正是为了钉死这个反向缺陷。
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("ORDER_DELIVERY_RATE", enabled: true), // 平台全局
- Metric("ORDER_DELIVERY_RATE", enabled: false, tenantId: TenantId, factoryId: FactoryId) // 租户覆盖:停用
- });
- Assert.Empty(result);
- }
- [Fact]
- public void T5_TenantOverrideEnable_ShowsMetric_EvenWhenGlobalDisabled()
- {
- // 反向:全局停用 + 租户启用 → 租户可见。
- // 覆盖语义由 OrderByDescending(FactoryId).ThenByDescending(TenantId) 承载,本用例确认它对两个方向都成立。
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("PO_DELIVERY_RATE", enabled: false),
- Metric("PO_DELIVERY_RATE", enabled: true, tenantId: TenantId, factoryId: FactoryId)
- });
- Assert.Equal(new[] { "PO_DELIVERY_RATE" }, CodesOf(result));
- }
- [Fact]
- public void T5b_EachMetricCode_ResolvesToExactlyOneEffectiveRow()
- {
- // 覆盖解析必须"每个 code 只留一条",否则同一 KPI 会在卡片上重复出现。
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("WO_COMPLETION_RATE", enabled: true),
- Metric("WO_COMPLETION_RATE", enabled: true, tenantId: TenantId),
- Metric("WO_COMPLETION_RATE", enabled: true, tenantId: TenantId, factoryId: FactoryId)
- });
- Assert.Single(result);
- // 工厂行优先级最高。
- Assert.Equal(FactoryId, result[0].FactoryId);
- }
- // ============================================================
- // E. Rule 机制字典不受影响
- // ============================================================
- [Fact]
- public void T6_RuleMechanismDictionary_IsUntouchedByTheGate()
- {
- // 运行库中 ado_s8_monitor_metric 有 8 条 enabled=1 的机制字典项(DATE / VALUE_RANGE),
- // 由 S8ConfigDraftService(Rule 向导)消费。本门禁只在结果 KPI 路径调用,
- // 这里断言:即使把它们混进候选集,也只会因 IsResultKpi=false 被调用方排除,
- // 而不会被本方法当作结果 KPI 返回。
- var mixed = new[]
- {
- Metric("ORDER_DUE_AT", enabled: true, isResultKpi: false, mechanism: "DATE"),
- Metric("INV_QTY", enabled: true, isResultKpi: false, mechanism: "VALUE_RANGE"),
- Metric("ORDER_FLOW_STAGE_VARIANCE", enabled: true, isResultKpi: false, mechanism: "VALUE_RANGE"),
- Metric("ORDER_DELIVERY_RATE", enabled: true)
- };
- // 模拟调用方 SQL 的 mechanism + is_result_kpi 过滤。
- var candidates = mixed.Where(m => m.Mechanism == "RATIO" && m.IsResultKpi);
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(candidates);
- Assert.Equal(new[] { "ORDER_DELIVERY_RATE" }, CodesOf(result));
- // 机制字典项全部保持 enabled=true,未被本批改动。
- Assert.All(mixed.Where(m => !m.IsResultKpi), m => Assert.True(m.Enabled));
- }
- // ============================================================
- // 排序稳定性(卡片顺序不应因门禁而乱)
- // ============================================================
- [Fact]
- public void EnabledMetrics_KeepSortNoThenCodeOrdering()
- {
- var result = S8MonitoringService.ResolveEnabledEffectiveResultKpis(new[]
- {
- Metric("B_RATE", enabled: true, sortNo: 2),
- Metric("A_RATE", enabled: true, sortNo: 2),
- Metric("Z_RATE", enabled: true, sortNo: 1),
- Metric("X_RATE", enabled: false, sortNo: 0) // 停用项不参与排序结果
- });
- Assert.Equal(new[] { "Z_RATE", "A_RATE", "B_RATE" }, CodesOf(result));
- }
- }
|