| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- using System.Reflection;
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Entity;
- using SqlSugar;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
- /// <summary>
- /// Phase 2 结构级验证(反射,无 DB):断言 L3 影子账实体的表名、复合唯一键维度、
- /// tenant_id + domain_code 列、lot_serial/refs 空批次唯一语义(NOT NULL DEFAULT '')、条码非唯一。
- /// 复合唯一键维度全部据 Schema 实核(LIVE SP 真定义)确定,此测试把「唯一键契约」固化。
- /// </summary>
- public class AdoInventoryEntitiesStructureTests
- {
- private static string TableName(Type t)
- => t.GetCustomAttribute<SugarTable>()?.TableName;
- private static SugarColumn Column(Type t, string prop)
- => t.GetProperty(prop)?.GetCustomAttribute<SugarColumn>();
- private static List<HashSet<string>> UniqueKeyPropSets(Type t)
- => t.GetCustomAttributes<SugarIndexAttribute>()
- .Where(ix => ix.IsUnique)
- .Select(ix => new HashSet<string>(ix.IndexFields.Keys))
- .ToList();
- private static bool HasUniqueKey(Type t, params string[] props)
- {
- var want = new HashSet<string>(props);
- return UniqueKeyPropSets(t).Any(s => s.SetEquals(want));
- }
- private static bool AnyUniqueContains(Type t, string prop)
- => UniqueKeyPropSets(t).Any(s => s.Contains(prop));
- [Theory]
- [InlineData(typeof(AdoInventoryMaster), "ado_inventory_master")]
- [InlineData(typeof(AdoInventoryLocationDetail), "ado_inventory_location_detail")]
- [InlineData(typeof(AdoInventoryTransaction), "ado_inventory_transaction")]
- [InlineData(typeof(AdoBarcodeCard), "ado_barcode_card")]
- [InlineData(typeof(AdoBarcodeCardTransaction), "ado_barcode_card_transaction")]
- [InlineData(typeof(AdoIqcInventoryPosting), "ado_iqc_inventory_posting")]
- public void Entity_HasExpectedDopOwnedTableName(Type t, string expected)
- {
- var name = TableName(t);
- Assert.Equal(expected, name);
- // DOP-owned 前缀,且不得与旧 WMS 表同名
- Assert.StartsWith("ado_", name);
- Assert.DoesNotContain(name, new[] { "InvMaster", "LocationDetail", "InvTransHist", "MissedPrint" });
- }
- [Theory]
- [InlineData(typeof(AdoInventoryMaster))]
- [InlineData(typeof(AdoInventoryLocationDetail))]
- [InlineData(typeof(AdoInventoryTransaction))]
- [InlineData(typeof(AdoBarcodeCard))]
- [InlineData(typeof(AdoBarcodeCardTransaction))]
- [InlineData(typeof(AdoIqcInventoryPosting))]
- public void Entity_HasExplicitTenantAndDomainColumns(Type t)
- {
- Assert.Equal("tenant_id", Column(t, nameof(AdoInvEntityBase.TenantId))?.ColumnName);
- var domain = Column(t, nameof(AdoInvEntityBase.DomainCode));
- Assert.Equal("domain_code", domain?.ColumnName);
- Assert.False(domain.IsNullable); // domain 非空
- Assert.True(typeof(AdoInvEntityBase).IsAssignableFrom(t)); // 统一基类
- }
- [Fact]
- public void InventoryMaster_UniqueKey_TenantDomainItemLocation_NoLot()
- {
- // 实核:InvMaster 自然键 = Domain+ItemNum+Location(不含批次)
- Assert.True(HasUniqueKey(typeof(AdoInventoryMaster),
- nameof(AdoInvEntityBase.TenantId), nameof(AdoInvEntityBase.DomainCode),
- nameof(AdoInventoryMaster.ItemNum), nameof(AdoInventoryMaster.Location)));
- }
- [Fact]
- public void LocationDetail_UniqueKey_SixDims_And_LotRefsNotNullDefaultEmpty()
- {
- var t = typeof(AdoInventoryLocationDetail);
- // 实核:六维唯一 tenant+domain+item+location+lot_serial+refs
- Assert.True(HasUniqueKey(t,
- nameof(AdoInvEntityBase.TenantId), nameof(AdoInvEntityBase.DomainCode),
- nameof(AdoInventoryLocationDetail.ItemNum), nameof(AdoInventoryLocationDetail.Location),
- nameof(AdoInventoryLocationDetail.LotSerial), nameof(AdoInventoryLocationDetail.Refs)));
- // ⚠️ 关键陷阱:lot_serial / refs 必须 NOT NULL DEFAULT ''(空串占位唯一,非 NULL)
- var lot = Column(t, nameof(AdoInventoryLocationDetail.LotSerial));
- Assert.Equal("lot_serial", lot.ColumnName);
- Assert.False(lot.IsNullable);
- Assert.Equal("", lot.DefaultValue);
- var refs = Column(t, nameof(AdoInventoryLocationDetail.Refs));
- Assert.Equal("refs", refs.ColumnName);
- Assert.False(refs.IsNullable);
- Assert.Equal("", refs.DefaultValue);
- }
- [Fact]
- public void InventoryTransaction_StructuralDedup_GroupSeq()
- {
- // append-only;结构层防重 = tenant+transaction_group_id+seq(不替代 Phase 4 幂等)
- Assert.True(HasUniqueKey(typeof(AdoInventoryTransaction),
- nameof(AdoInvEntityBase.TenantId),
- nameof(AdoInventoryTransaction.TransactionGroupId),
- nameof(AdoInventoryTransaction.Seq)));
- }
- [Fact]
- public void BarcodeCard_SourceRebackfillUnique_And_BarcodeNotUnique()
- {
- var t = typeof(AdoBarcodeCard);
- // 回灌幂等键 tenant+source_domain+source_rec_id
- Assert.True(HasUniqueKey(t,
- nameof(AdoInvEntityBase.TenantId),
- nameof(AdoBarcodeCard.SourceDomain),
- nameof(AdoBarcodeCard.SourceRecId)));
- // 实核:barcode 会重复 → 绝不可作唯一
- Assert.False(AnyUniqueContains(t, nameof(AdoBarcodeCard.Barcode)));
- }
- [Fact]
- public void IqcInventoryPosting_IdempotencyKey_TenantDomainFbillno()
- {
- // 统一 IQC 入库事件幂等键(DB UNIQUE)
- Assert.True(HasUniqueKey(typeof(AdoIqcInventoryPosting),
- nameof(AdoInvEntityBase.TenantId), nameof(AdoInvEntityBase.DomainCode),
- nameof(AdoIqcInventoryPosting.FbillNo)));
- // 过账模式默认 SHADOW(本批次最高只允许 SHADOW)
- Assert.Equal("SHADOW", Column(typeof(AdoIqcInventoryPosting), nameof(AdoIqcInventoryPosting.PostingMode)).DefaultValue);
- }
- }
|