using Xunit;
namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
///
/// 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。
///
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);
}
/// 生成时仍写 '检验中',是唯一的初始态写入点。
[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);
}
/// 详情接口仍原样返回 FBILLSTATUS,不得在 GET 侧动态伪造完成态。
[Fact]
public void DetailApi_ReturnsRawBillStatus_NoDerivedCompletion()
{
var s = InspBillService();
Assert.Contains("ib.FBILLSTATUS AS BillStatus", s);
Assert.DoesNotContain("检验完成", s);
}
/// B-2 检规快照逻辑未被本次修改波及。
[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));
}
}