ApprovalFlowTenantIdContractTests.cs 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. using System.Reflection;
  2. using Admin.NET.Core;
  3. using SqlSugar;
  4. using Xunit;
  5. using AF = Admin.NET.Plugin.ApprovalFlow.ApprovalFlow;
  6. namespace Admin.NET.Plugin.AiDOP.Tests.ApprovalFlow;
  7. /// <summary>
  8. /// ApprovalFlow.TenantId 的属性契约。
  9. /// <para>本实体曾用「重新声明同名属性」隐藏 <see cref="EntityBaseTenantOrgDel"/> 的 TenantId(CS0114)。
  10. /// 隐藏会造成两处静默故障:<c>((ITenantIdFilter)flow).TenantId</c> 落到基类那个永不赋值的字段上(实测读回 null),
  11. /// 以及丢掉基类 <c>IsOnlyIgnoreUpdate = true</c>,使整实体 UPDATE 有机会改写租户归属。</para>
  12. /// <para>两者当时都没有代码路径触发,编译器也只给一条 warning —— 正因为无声,才需要契约测试钉住。</para>
  13. /// </summary>
  14. public class ApprovalFlowTenantIdContractTests
  15. {
  16. private static PropertyInfo TenantIdProperty =>
  17. typeof(AF).GetProperty(nameof(ITenantIdFilter.TenantId))!;
  18. /// <summary>TenantId 必须是 override,而不是隐藏基类属性。</summary>
  19. [Fact]
  20. public void TenantId_Overrides_BaseProperty_RatherThanHidingIt()
  21. {
  22. var getter = TenantIdProperty.GetGetMethod()!;
  23. Assert.True(getter.IsVirtual, "TenantId 必须是 virtual/override 链的一环");
  24. // override 的 getter 其 GetBaseDefinition() 指回基类;隐藏(new)则指向自己。
  25. Assert.Equal(typeof(EntityBaseTenantOrgDel), getter.GetBaseDefinition().DeclaringType);
  26. }
  27. /// <summary>子类、接口、基类三条访问路径必须读到同一个值(含 null)。</summary>
  28. [Theory]
  29. [InlineData(797403760988229L)]
  30. [InlineData(null)]
  31. public void TenantId_IsConsistent_AcrossDerived_Interface_And_Base(long? value)
  32. {
  33. var flow = new AF { TenantId = value };
  34. Assert.Equal(value, flow.TenantId);
  35. Assert.Equal(value, ((ITenantIdFilter)flow).TenantId);
  36. Assert.Equal(value, ((EntityBaseTenantOrgDel)flow).TenantId);
  37. }
  38. /// <summary>反向:经接口写入,子类必须读得到——泛型基础设施按 ITenantIdFilter 赋值时不能落空。</summary>
  39. [Fact]
  40. public void TenantId_WrittenThroughInterface_IsVisibleOnDerived()
  41. {
  42. var flow = new AF();
  43. ((ITenantIdFilter)flow).TenantId = 797403760988229L;
  44. Assert.Equal(797403760988229L, flow.TenantId);
  45. }
  46. /// <summary>NULL 全局流程语义:列必须可空,且 ORM 不得把它推导成 NOT NULL。</summary>
  47. [Fact]
  48. public void TenantId_StaysNullable_ForGlobalFlowSemantics()
  49. {
  50. Assert.Equal(typeof(long?), TenantIdProperty.PropertyType);
  51. var sugarColumn = TenantIdProperty
  52. .GetCustomAttributes(typeof(SugarColumn), inherit: true)
  53. .OfType<SugarColumn>()
  54. .SingleOrDefault();
  55. Assert.NotNull(sugarColumn);
  56. Assert.True(sugarColumn!.IsNullable, "TenantId IS NULL 表示全局流程,不能推导成 NOT NULL");
  57. }
  58. /// <summary>租户归属只在插入时写定:任何整实体 UPDATE 都不得把 TenantId 带进 SET。</summary>
  59. [Fact]
  60. public void TenantId_IsExcluded_FromWholeEntityUpdate()
  61. {
  62. var sugarColumn = TenantIdProperty
  63. .GetCustomAttributes(typeof(SugarColumn), inherit: true)
  64. .OfType<SugarColumn>()
  65. .Single();
  66. Assert.True(sugarColumn.IsOnlyIgnoreUpdate, "IsOnlyIgnoreUpdate 承自基类,丢掉它整实体更新就能改写租户归属");
  67. }
  68. /// <summary>ORM 只能看到一个 TenantId,不允许父子双属性同时进入列元数据。</summary>
  69. [Fact]
  70. public void TenantId_MapsToExactlyOneClrProperty()
  71. {
  72. var properties = typeof(AF)
  73. .GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy)
  74. .Where(p => p.Name == nameof(ITenantIdFilter.TenantId))
  75. .ToList();
  76. Assert.Single(properties);
  77. Assert.Equal(typeof(AF), properties[0].DeclaringType);
  78. }
  79. }