|
|
@@ -0,0 +1,124 @@
|
|
|
+using Xunit;
|
|
|
+
|
|
|
+namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// P1-A 源码契约测试:qms_qcp_inspbill.FBILLSTATUS 完成态回写。
|
|
|
+///
|
|
|
+/// 固化四条不变量:
|
|
|
+/// ① 终态只由流程结束回调 OnFlowCompleted 写,且仅 Approved 才写;
|
|
|
+/// ② NG 单不得在主管通过(N2)时提前写完成 —— 靠「只在实例终结回调里写」结构性保证,
|
|
|
+/// 故 supervisor-approve / sqe-submit-disposition 所在的 Service 内不得出现 FBILLSTATUS 写入;
|
|
|
+/// ③ 取值复用既有 done-word '检验完成',不自造新枚举、不采用无写入者的 COMPLETED/C/A/NG;
|
|
|
+/// ④ 回写必须租户隔离,且不越界改 pd/clfs/FINSPECTSTATUS。
|
|
|
+/// </summary>
|
|
|
+public class IqcBillStatusCompletionContractTests
|
|
|
+{
|
|
|
+ private static string Handler() => File.ReadAllText(FindFile(
|
|
|
+ "server", "Plugins", "Admin.NET.Plugin.AiDOP", "MaterialWarehouse", "IqcInspBillFlowBizHandler.cs"));
|
|
|
+
|
|
|
+ private static string FlowService() => File.ReadAllText(FindFile(
|
|
|
+ "server", "Plugins", "Admin.NET.Plugin.AiDOP", "MaterialWarehouse", "IqcInspBillFlowService.cs"));
|
|
|
+
|
|
|
+ private static string TaskEntryService() => File.ReadAllText(FindFile(
|
|
|
+ "server", "Plugins", "Admin.NET.Plugin.AiDOP", "MaterialWarehouse", "IqcTaskEntryService.cs"));
|
|
|
+
|
|
|
+ private static string InspBillService() => File.ReadAllText(FindFile(
|
|
|
+ "server", "Plugins", "Admin.NET.Plugin.AiDOP", "MaterialWarehouse", "IqcInspBillService.cs"));
|
|
|
+
|
|
|
+ // ── ① 终态只在 OnFlowCompleted 写,且仅 Approved ──
|
|
|
+ [Fact]
|
|
|
+ public void Handler_WritesBillStatus_OnlyOnApprovedCompletion()
|
|
|
+ {
|
|
|
+ var s = Handler();
|
|
|
+ Assert.Contains("public async Task OnFlowCompleted", s);
|
|
|
+ Assert.Contains("if (finalStatus != FlowInstanceStatusEnum.Approved) return;", s);
|
|
|
+ Assert.Contains("UPDATE qms_qcp_inspbill SET FBILLSTATUS=@Status", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── ② NG 不得提前完成:Service 侧不得出现 FBILLSTATUS 写入 ──
|
|
|
+ [Fact]
|
|
|
+ public void FlowService_DoesNotWriteBillStatus()
|
|
|
+ {
|
|
|
+ var s = FlowService();
|
|
|
+ Assert.DoesNotContain("SET FBILLSTATUS", s);
|
|
|
+ Assert.DoesNotContain("FBILLSTATUS=", s);
|
|
|
+ // submit-result 仍只写这四列
|
|
|
+ Assert.Contains("UPDATE qms_qcp_inspbill SET pd=@pd, dhsl=@dhsl, bhgsl=@bhgsl, clfs=@clfs", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>生成时仍写 '检验中',是唯一的初始态写入点。</summary>
|
|
|
+ [Fact]
|
|
|
+ public void GenerateInspBill_StillWritesInProgressStatus()
|
|
|
+ {
|
|
|
+ var s = TaskEntryService();
|
|
|
+ Assert.Contains("'检验中'", s);
|
|
|
+ Assert.DoesNotContain("SET FBILLSTATUS", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── ③ 取值复用既有 done-word,不自造枚举 ──
|
|
|
+ [Fact]
|
|
|
+ public void BillStatusDone_ReusesExistingVocabulary()
|
|
|
+ {
|
|
|
+ var s = Handler();
|
|
|
+ Assert.Contains("public const string BillStatusDone = \"检验完成\";", s);
|
|
|
+ Assert.Contains("IqcInspBillFlowConst.BillStatusDone", s);
|
|
|
+ // 不得引入库内那些无代码写入者的历史 seed 值
|
|
|
+ Assert.DoesNotContain("\"COMPLETED\"", s);
|
|
|
+ Assert.DoesNotContain("\"CREATED\"", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ── ④ 租户隔离 + 不越界 ──
|
|
|
+ [Fact]
|
|
|
+ public void Handler_CompletionWrite_IsTenantScoped()
|
|
|
+ {
|
|
|
+ var s = Handler();
|
|
|
+ Assert.Contains("WHERE id=@id AND tenant_id=@TenantId", s);
|
|
|
+ Assert.Contains("_userManager.TenantId", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void Handler_DoesNotTouchOtherBusinessFields()
|
|
|
+ {
|
|
|
+ var s = Handler();
|
|
|
+ // 不改判定/处理方式/审核人/报检分录状态
|
|
|
+ Assert.DoesNotContain("SET pd=", s);
|
|
|
+ Assert.DoesNotContain("SET clfs=", s);
|
|
|
+ Assert.DoesNotContain("FAUDITORID=", s);
|
|
|
+ Assert.DoesNotContain("qms_qcp_insappnentry", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>详情接口仍原样返回 FBILLSTATUS,不得在 GET 侧动态伪造完成态。</summary>
|
|
|
+ [Fact]
|
|
|
+ public void DetailApi_ReturnsRawBillStatus_NoDerivedCompletion()
|
|
|
+ {
|
|
|
+ var s = InspBillService();
|
|
|
+ Assert.Contains("ib.FBILLSTATUS AS BillStatus", s);
|
|
|
+ Assert.DoesNotContain("检验完成", s);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>B-2 检规快照逻辑未被本次修改波及。</summary>
|
|
|
+ [Fact]
|
|
|
+ public void SpecSnapshotColumns_StillWrittenOnlyAtGeneration()
|
|
|
+ {
|
|
|
+ var gen = TaskEntryService();
|
|
|
+ Assert.Contains("jgbh, jgbb, jgsxrq, jygfid", gen);
|
|
|
+ Assert.Contains("spec.IsMatched", gen);
|
|
|
+
|
|
|
+ var handler = Handler();
|
|
|
+ Assert.DoesNotContain("jgbh", handler);
|
|
|
+ Assert.DoesNotContain("jygfid", handler);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static string FindFile(params string[] parts)
|
|
|
+ {
|
|
|
+ var dir = new DirectoryInfo(AppContext.BaseDirectory);
|
|
|
+ while (dir != null)
|
|
|
+ {
|
|
|
+ var candidate = Path.Combine(new[] { dir.FullName }.Concat(parts).ToArray());
|
|
|
+ if (File.Exists(candidate)) return candidate;
|
|
|
+ dir = dir.Parent;
|
|
|
+ }
|
|
|
+ throw new FileNotFoundException(string.Join("/", parts));
|
|
|
+ }
|
|
|
+}
|