using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.S6.Manufacturing; /// /// S6 Phase 1D 桥接契约测试:Phase 1C bill 接入 ApprovalFlow(BizType=S6_PROCESS_INSPECTION)。 /// 覆盖:独立 BizType 绑 bill.id、提交门槛 COMPLETED、FAIL→disposition_required、质量结果不改写、tenant/幂等/N1=Initiator。 /// public class S6ProcessInspectionReviewContractTests { private static string Svc() => File.ReadAllText(FindFile("server", "Plugins", "Admin.NET.Plugin.AiDOP", "Manufacturing", "S6ProcessInspectionReviewService.cs")); private static string Handler() => File.ReadAllText(FindFile("server", "Plugins", "Admin.NET.Plugin.AiDOP", "Manufacturing", "S6ProcessInspectionFlowBizHandler.cs")); private static string Const() => File.ReadAllText(FindFile("server", "Plugins", "Admin.NET.Plugin.AiDOP", "Manufacturing", "S6ProcessInspectionFlowConst.cs")); private static string Seed() => File.ReadAllText(FindFile("server", "Plugins", "Admin.NET.Plugin.AiDOP", "Infrastructure", "S6ProcessInspectionFlowSeed.cs")); [Fact] public void BizType_IsIndependent_BoundToProcessInspectionBill() { Assert.Contains("\"S6_PROCESS_INSPECTION\"", Const()); var s = Svc(); // business_id = process_inspection_bill.id(StartFlow BizId=input.BillId),不绑 qms_gcjyd Assert.Contains("BizId = input.BillId", s); Assert.Contains("FROM ado_s6_process_inspection_bill", s); Assert.DoesNotContain("qms_gcjyd", s); } [Fact] public void Submit_Gate_RequiresCompleted() { var s = Svc(); Assert.Contains("INSPECTION_NOT_COMPLETED", s); Assert.Contains("StatusCompleted", s); } [Fact] public void Gateway_FailDrivesDisposition_FromOverallResult() { // 网关判据 disposition_required 由 overall_result=FAIL 决定,只读、不改写质量结果 var h = Handler(); Assert.Contains("overall_result", h); Assert.Contains("ResultFail", h); Assert.Contains("[\"disposition_required\"]", h); // Handler 不 UPDATE 质量结果 Assert.DoesNotContain("UPDATE ado_s6_process_inspection_result", h); } [Fact] public void Disposition_DoesNotOverwrite_QualityResult() { var s = Svc(); // 处置落 ado_s6_ipqc_quality_disposition,绝不 UPDATE overall_result(§20 硬门) Assert.Contains("AdoS6IpqcQualityDisposition", s); Assert.DoesNotContain("UPDATE ado_s6_process_inspection_result SET overall_result", s); Assert.DoesNotContain("SET overall_result", s); } [Fact] public void Idempotent_And_TenantSafe_And_Transactional() { var s = Svc(); Assert.Contains("已在审核中,请勿重复提交", s); // 一单一 active flow Assert.Contains("ResolveTenantOrThrow", s); Assert.Contains("UseTranAsync", s); Assert.DoesNotContain("tenant_id IS NULL", s); } [Fact] public void N1_IsInitiator_ReuseSupervisorAndQeRoles() { var seed = Seed(); Assert.Contains("nameof(ApproverTypeEnum.Initiator)", seed); // N1=发起人 Assert.Contains("RoleSupervisorId", seed); // N2 复用 IPQC 主管角色 Assert.Contains("RoleQualityEngineerId", seed); // N3 复用 IPQC QE 角色 // 复用 IPQC 角色 Id(不新造角色) Assert.Contains("IpqcInspectionFlowConst.RoleSupervisorId", Const()); } 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)); } }