S6ProductionInstructionContractTests.cs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. using Xunit;
  2. namespace Admin.NET.Plugin.AiDOP.Tests.S6.Manufacturing;
  3. /// <summary>
  4. /// S6 Phase 1A · 生产指令中台消费层契约测试(源码契约断言,无需活库)。
  5. /// 覆盖:STD 扩列 + specification enrich(Domain+ItemNum)、消费层强租户隔离、只读中台不直连源库、真分页、筛选。
  6. /// </summary>
  7. public class S6ProductionInstructionContractTests
  8. {
  9. private static string ReadS2Transform() => File.ReadAllText(FindFile(
  10. "server", "Plugins", "Admin.NET.Plugin.AiDOP", "Production", "S2MdpSyncTransformService.cs"));
  11. private static string ReadService() => File.ReadAllText(FindFile(
  12. "server", "Plugins", "Admin.NET.Plugin.AiDOP", "Manufacturing", "S6ProductionInstructionService.cs"));
  13. [Fact]
  14. public void Std_Schedule_HasNewNullableColumns()
  15. {
  16. var s2 = ReadS2Transform();
  17. Assert.Contains("specification VARCHAR(200) NULL", s2);
  18. Assert.Contains("lot_serial VARCHAR(100) NULL", s2);
  19. Assert.Contains("drawing_no VARCHAR(100) NULL", s2);
  20. Assert.Contains("project VARCHAR(200) NULL", s2);
  21. Assert.Contains("work_order_type VARCHAR(50) NULL", s2);
  22. Assert.Contains("labor_variance DECIMAL(18,6) NULL", s2);
  23. // 唯一键不变
  24. Assert.Contains("uk_std_work_order_schedule (tenant_id, work_order)", s2);
  25. }
  26. [Fact]
  27. public void Specification_EnrichedFromItemMaster_ByDomainAndItemNum_NotItemNumOnly()
  28. {
  29. var s2 = ReadS2Transform();
  30. // specification 来自 ItemMaster.Descr1
  31. Assert.Contains("im.Descr1", s2);
  32. Assert.Contains("FROM ItemMaster", s2);
  33. // 关联必须同时按 Domain + ItemNum(去重取一,实测同键 Descr1 无冲突)
  34. Assert.Contains("GROUP BY `Domain`, ItemNum", s2);
  35. Assert.Contains("im.`Domain` = JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.Domain'))", s2);
  36. Assert.Contains("im.ItemNum = JSON_UNQUOTE(JSON_EXTRACT(s.raw_data,'$.ItemNum'))", s2);
  37. // 新列进入 upsert
  38. Assert.Contains("specification=VALUES(specification)", s2);
  39. Assert.Contains("lot_serial=VALUES(lot_serial)", s2);
  40. }
  41. [Fact]
  42. public void EnvelopeSourcedColumns_MappedFromRawData()
  43. {
  44. var s2 = ReadS2Transform();
  45. Assert.Contains("JSON_EXTRACT(s.raw_data,'$.LotSerial')", s2);
  46. Assert.Contains("JSON_EXTRACT(s.raw_data,'$.Drawing')", s2);
  47. Assert.Contains("JSON_EXTRACT(s.raw_data,'$.Project')", s2);
  48. Assert.Contains("JSON_EXTRACT(s.raw_data,'$.WoTyped')", s2);
  49. Assert.Contains("JSON_EXTRACT(s.raw_data,'$.LbrVar')", s2);
  50. }
  51. [Fact]
  52. public void Consumer_UsesStrictTenantScope_OnEverySql()
  53. {
  54. var svc = ReadService();
  55. Assert.Contains("AidopTenantScope.ResolveOrThrow", svc);
  56. Assert.Contains("m.tenant_id = @TenantId", svc);
  57. // COUNT / PAGE / DETAIL 都带租户过滤:至少出现 2 处显式 tenant 谓词(page where + detail where)
  58. var occurrences = svc.Split("m.tenant_id = @TenantId").Length - 1;
  59. Assert.True(occurrences >= 2, $"expected tenant predicate on page+detail, found {occurrences}");
  60. }
  61. [Fact]
  62. public void Consumer_ReadsMdpStdOnly_NeverSourceTables()
  63. {
  64. var svc = ReadService();
  65. Assert.Contains("FROM mdp_std_work_order_schedule", svc);
  66. Assert.DoesNotContain("WorkOrdMaster", svc);
  67. Assert.DoesNotContain("ItemMaster", svc);
  68. Assert.DoesNotContain("mes_morder", svc);
  69. }
  70. [Fact]
  71. public void Consumer_HasNoTenantFallback_AndNoSelectStar()
  72. {
  73. var svc = ReadService();
  74. Assert.DoesNotContain("tenant_id IS NULL", svc);
  75. Assert.DoesNotContain("tenant_id = 0", svc);
  76. Assert.DoesNotContain("SELECT *", svc);
  77. // 不硬编码工厂
  78. Assert.DoesNotContain("factory_id = 1", svc);
  79. Assert.DoesNotContain("factory_id = 8010", svc);
  80. }
  81. [Fact]
  82. public void Consumer_TruePagination_AndFilters()
  83. {
  84. var svc = ReadService();
  85. Assert.Contains("LIMIT {pageSize} OFFSET {offset}", svc);
  86. Assert.Contains("SELECT COUNT(1) FROM mdp_std_work_order_schedule", svc);
  87. Assert.Contains("m.work_order LIKE @WorkOrderNo", svc);
  88. Assert.Contains("m.lot_serial LIKE @LotSerial", svc);
  89. Assert.Contains("m.item_code LIKE @ItemCode", svc);
  90. Assert.Contains("m.status = @Status", svc);
  91. Assert.Contains("m.order_date >= @OrderDateStart", svc);
  92. Assert.Contains("DATE_ADD(@OrderDateEnd, INTERVAL 1 DAY)", svc);
  93. Assert.Contains("ORDER BY m.order_date DESC, m.work_order DESC", svc);
  94. }
  95. private static string FindFile(params string[] parts)
  96. {
  97. var dir = new DirectoryInfo(AppContext.BaseDirectory);
  98. while (dir != null)
  99. {
  100. var candidate = Path.Combine(new[] { dir.FullName }.Concat(parts).ToArray());
  101. if (File.Exists(candidate)) return candidate;
  102. dir = dir.Parent;
  103. }
  104. throw new FileNotFoundException(string.Join("/", parts));
  105. }
  106. }