| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- using Admin.NET.Plugin.AiDOP.Controllers.S8;
- using System.Reflection;
- using System.Text.RegularExpressions;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.S8;
- /// <summary>
- /// S8-P1-FACTUAL-DISPLAY-1:订单执行档案的取数 Authority 守卫。
- ///
- /// <para>档案页的三张 KPI 卡(平均响应 / 处理 / 损失时间)与订单计数,
- /// 全部建立在 <c>GET /api/aidop/s8/order-flow/orders</c> 的返回上。
- /// 该路由必须走 <see cref="Service.S8.OrderFlow.S8RealOrderArchiveService"/>
- /// (读 <c>mdp_std_so</c> + <c>dwd_*</c>),<b>不得</b>回退到读
- /// <c>ado_s8_order_flow_order</c> 的 SEED 路径 —— 那张表唯一的写入方是 SeedData。</para>
- ///
- /// <para>SEED 路径(<c>S8OrderFlowService</c>)本身保留,供链路全景等既有消费方与回归 fixture 使用;
- /// 本测试钉住的是「两路不混用」,不是「删掉 SEED 路径」。</para>
- ///
- /// <para>手法沿用本仓既有范式(反射元数据 + 去注释源码契约),
- /// 与 <c>S8DebugTenantGuardTests</c> / <c>S8KpiProvenanceTests</c> 一致:
- /// 服务依赖 SqlSugar 实库,进程内无法构造。</para>
- /// </summary>
- public class S8OrderArchiveAuthorityTests
- {
- private static string PluginRoot => Path.GetFullPath(
- Path.Combine(AppContext.BaseDirectory, "../../../../Admin.NET.Plugin.AiDOP"));
- private static string ReadCode(string relativePath)
- {
- var raw = File.ReadAllText(Path.Combine(PluginRoot, relativePath));
- var noBlock = Regex.Replace(raw, @"/\*.*?\*/", string.Empty, RegexOptions.Singleline);
- return Regex.Replace(noBlock, @"^\s*//.*$", string.Empty, RegexOptions.Multiline);
- }
- /// <summary>SEED 订单链路实体:不得出现在真实档案取数链上。</summary>
- private static readonly string[] SeedOrderFlowEntities =
- {
- "AdoS8OrderFlowOrder",
- "AdoS8OrderFlowStage",
- "AdoS8OrderFlowProcurementPivot",
- "AdoS8OrderFlowManufacturingProcess",
- };
- // ───────── ⑦ OrderArchiveKpi_UsesRealOrderAuthority ─────────
- /// <summary><c>/orders</c> 必须由 REAL 服务提供。</summary>
- [Fact]
- public void OrdersEndpoint_IsServedByRealArchiveService()
- {
- var code = ReadCode("Controllers/S8/AdoS8OrderFlowController.cs");
- var idx = code.IndexOf("[HttpGet(\"orders\")]", StringComparison.Ordinal);
- Assert.True(idx > 0, "未找到 /orders 路由");
- var action = code[idx..Math.Min(idx + 420, code.Length)];
- Assert.Contains("_realSvc.GetRealOrdersAsync", action);
- Assert.DoesNotContain("_svc.GetOrdersAsync", action);
- }
- // ───────── ⑥ OrderArchiveKpi_DoesNotReadSeedRepository ─────────
- /// <summary>REAL 档案服务不得依赖任何 SEED 订单链路仓储。</summary>
- [Fact]
- public void RealArchiveService_DoesNotReadSeedOrderFlowTables()
- {
- var code = ReadCode("Service/S8/OrderFlow/S8RealOrderArchiveService.cs");
- // 判据落在「仓储依赖」与「物理表名」上,不能裸搜实体名 ——
- // 本服务的返回类型叫 AdoS8OrderFlowOrderListItemDto / ...DetailDto,
- // 那是 DTO,不是 SEED 实体;裸子串会把一条真守卫变成噪音。
- foreach (var entity in SeedOrderFlowEntities)
- Assert.DoesNotContain($"SqlSugarRepository<{entity}>", code);
- Assert.DoesNotContain("ado_s8_order_flow", code);
- // 更强的结构性保证:本服务根本不注入任何仓储,只走 Ado 原生 SQL。
- Assert.DoesNotContain("SqlSugarRepository", code);
- }
- /// <summary>反射层同一条守卫:构造函数不得注入 SEED 仓储。改文案绕不过去。</summary>
- [Fact]
- public void RealArchiveService_TakesNoSeedRepository()
- {
- var svc = typeof(AdoS8OrderFlowController).Assembly
- .GetType("Admin.NET.Plugin.AiDOP.Service.S8.OrderFlow.S8RealOrderArchiveService");
- Assert.NotNull(svc);
- var ctor = Assert.Single(svc!.GetConstructors());
- var injected = ctor.GetParameters()
- .Select(p => p.ParameterType.IsGenericType
- ? p.ParameterType.GetGenericArguments()[0].Name
- : p.ParameterType.Name)
- .ToList();
- foreach (var entity in SeedOrderFlowEntities)
- Assert.DoesNotContain(entity, injected);
- }
- /// <summary>
- /// 真实档案取数必须读数据中台标准层,且不得为了「去 seed」改成直连业务源表。
- /// </summary>
- [Fact]
- public void RealArchiveService_ReadsMdpAuthorityOnly()
- {
- var code = ReadCode("Service/S8/OrderFlow/S8RealOrderArchiveService.cs");
- Assert.Contains("mdp_std_so", code);
- foreach (var src in new[]
- {
- "PurOrdMaster", "PurOrdDetail", "WorkOrdMaster",
- "NbrMaster", "NbrDetail", "qms_", "crm_seorder", "mdp_stg_",
- })
- Assert.DoesNotContain(src, code);
- }
- // ───────── ⑨ TenantA_KpiCannotSeeTenantB ─────────
- /// <summary>档案取数的租户作用域一律服务端解析,不接受调用方传入。</summary>
- [Fact]
- public void RealArchive_ResolvesTenantServerSide()
- {
- var code = ReadCode("Service/S8/OrderFlow/S8RealOrderArchiveService.cs");
- Assert.Contains("AidopTenantScope.ResolveOrThrow", code);
- // 原生 SQL 用反引号写列名
- Assert.Contains("`tenant_id` = @TenantId", code);
- Assert.DoesNotContain("ClearFilter", code);
- }
- }
|