其他系列博文
背景 在上一篇博文中,我们通过使用物化路径模型实现了一个基于Abp的数据权限模块,在这一篇中博主将对模块的几个核心功能编写单元测试。
目标 覆盖以下几点核心功能:
提供默认的全局层级管理器GlobalHierarchyCodeManager
注入
层级树中可包含不同节点类型
数据过滤器在配置数据权限后自动开启
数据过滤其能根据用户权限进行正确的数据过滤
未开启数据权限过滤时用户默认应能获取全部数据
思路 对GlobalHierarchyCodeManager
自身的两个测试放在Hierarchy.Tests
项目中,而对于数据过滤器的测试放在EntityFrameworkCore.Tests
项目中,原因是数据过滤器是Ef Core
特有的。在EntityFrameworkCore.Tests
项目中,需要进行一些Mock数据的添加。
编码 首先创建Hierarchy.Tests
项目,模块文件及配置按照Abp的编码风格进行。
创建一个MyTestHasHierarchyRepository
类作为层级仓储的测试类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class MyTestHasHierarchyRepository : IHasHierarchyRepository <MyTestHierarchy >, ITransientDependency { public static readonly List<MyTestHierarchy> Nodes = new (); public Task DeleteNodeAsync (MyTestHierarchy node, CancellationToken cancellationToken = default ) { Nodes.RemoveAll(node => node.Id == node.Id); return Task.CompletedTask; } public Task<List<MyTestHierarchy>> GetAllChildrenAsync(MyTestHierarchy parent, bool isReadonly = true , CancellationToken cancellationToken = default ) { if (parent == null ) { return Task.FromResult(Nodes); } var children = Nodes.Where(node => node.HierarchyCode.StartsWith(parent.HierarchyCode) && node.Id != parent.Id); return Task.FromResult(children.ToList()); } public Task<string > GetLastChildCodeOrNullAsync (MyTestHierarchy parent, CancellationToken cancellationToken = default ) { var children = Nodes.Where(node => node.Parent?.Id == parent?.Id); var lastChild = children.OrderBy(child => child.HierarchyCode).LastOrDefault(); return Task.FromResult(lastChild?.HierarchyCode); } public Task SetHierarchyAsync (MyTestHierarchy node, MyTestHierarchy parent, string code, CancellationToken cancellationToken = default ) { var nodeInRepository = Nodes.Where(n => n.Id == node.Id); foreach (var n in nodeInRepository) { n.Parent = parent; n.HierarchyCode = code; }; return Task.CompletedTask; } }
其中的MyTestHierarchy
为节点测试类,可简单创建为:
1 2 3 4 5 6 7 8 public class MyTestHierarchy : IHasHierarchy { public int Id { get ; set ; } public string HierarchyCode { get ; set ; } public MyTestHierarchy Parent { get ; set ; } }
开始编写测试。
在测试文件中注入全局层级管理器:
1 2 3 4 5 6 private readonly IHierarchyCodeManager<IHasHierarchy> _globalManager;public GlobalHierarchyCodeManager_Tests ( ){ _globalManager = GetRequiredService<IHierarchyCodeManager<IHasHierarchy>>(); }
测试默认注册的IHierarchyCodeManager<IHasHierarchy>
为我们需要的GlobalHierarchyCodeManager
:
1 2 3 4 5 [Fact ] public void Should_Register_Global_Code_Manager_As_Default ( ){ ProxyHelper.UnProxy(_globalManager).ShouldBeOfType<GlobalHierarchyCodeManager>(); }
测试层级树中可包含不同节点类型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [Fact ] public async Task Should_Create_Hierarchy_Code_For_Nodes_Of_Different_Types ( ){ var node1 = new MyTestHierarchy1(); var node2 = new MyTestHierarchy2(); var node3 = new MyTestHierarchy2(); var node4 = new MyTestHierarchy2(); node1.HierarchyCode = await _globalManager.CreateCodeAsync(node1, null ); node2.HierarchyCode = await _globalManager.CreateCodeAsync(node2, node1); node3.HierarchyCode = await _globalManager.CreateCodeAsync(node3, node2); node4.HierarchyCode = await _globalManager.CreateCodeAsync(node4, node1); node2.HierarchyCode.ShouldStartWith(node1.HierarchyCode); node3.HierarchyCode.ShouldStartWith(node2.HierarchyCode); node4.HierarchyCode.ShouldStartWith(node1.HierarchyCode); node4.HierarchyCode.ShouldNotStartWith(node2.HierarchyCode); }
然后创建EntityFrameworkCore.Tests
项目,模块文件及配置同样按照Abp的编码风格进行。
创建两个测试领域实体HierarchicalUser
和Worksheet
,前者代表真实应用中我们的用户类,后者代表真实应用中具有获取权限的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class HierarchicalUser : Entity <Guid >, IHasHierarchy { [PreventUpdateIfDefault ] public string HierarchyCode { get ; set ; } public string Name { get ; set ; } private HierarchicalUser ( ) { } public HierarchicalUser (Guid id, string hierarchyCode, string name ) : base (id ) { HierarchyCode = hierarchyCode; Name = name; } } public class Worksheet : Entity <Guid >, IHasHierarchy { public string HierarchyCode { get ; set ; } public string Title { get ; set ; } private Worksheet ( ) { } public Worksheet (Guid id, string hierarchyCode, string title ) : base (id ) { HierarchyCode = hierarchyCode; Title = title; } }
创建DbContextTestAppDbContext
,并在Module文件中配置使用Sqlite数据库。
创建TestAppDataSeedContributor
类进行初始数据Seeding:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TestAppDataSeedContributor : IDataSeedContributor , ITransientDependency { public static HierarchicalUser HierarchyUser1 { get ; } = new (Guid.NewGuid(), string .Empty, "HierarchyUser1" ); public static HierarchicalUser HierarchyUser2 { get ; } = new (Guid.NewGuid(), "0001" , "HierarchyUser2" ); public static HierarchicalUser HierarchyUser3 { get ; } = new (Guid.NewGuid(), "0001.0002.0003" , "HierarchyUser3" ); public static Worksheet Worksheet1 { get ; } = new (Guid.NewGuid(), "0001.0002" , "Worksheet1" ); public static Worksheet Worksheet2 { get ; } = new (Guid.NewGuid(), "0003.0004" , "Worksheet2" ); private readonly IHierarchicalUserRepository _hierarchicalUserRepository; private readonly IWorksheetRepository _worksheetRepository; public TestAppDataSeedContributor ( IHierarchicalUserRepository hierarchicalUserRepository, IWorksheetRepository worksheetRepository ) { _hierarchicalUserRepository = hierarchicalUserRepository; _worksheetRepository = worksheetRepository; } public async Task SeedAsync (DataSeedContext context ) { await _hierarchicalUserRepository.InsertAsync(HierarchyUser1); await _hierarchicalUserRepository.InsertAsync(HierarchyUser2); await _hierarchicalUserRepository.InsertAsync(HierarchyUser3); await _worksheetRepository.InsertAsync(Worksheet1); await _worksheetRepository.InsertAsync(Worksheet2); } }
在Module文件中,我们需要用HierarchicalUser
类开启数据权限配置:
1 context.Services.AddHierarchicalUser<HierarchicalUser, HierarchicalUserRepository>();
在测试文件中,
验证一下权限数据过滤器已开启:
1 2 3 4 5 6 [Fact ] public void Should_Enabled_UseHierarchyUser_Option ( ){ var option = GetRequiredService<IOptions<AbpXSecurityOptions>>().Value; option.UseHierarchyUser.ShouldBeTrue(); }
为了便于测试,我们不引入真实的用户身份信息验证流程,直接在测试类中Mock需要的Claim:
1 2 3 4 5 6 7 8 9 private ICurrentUser _fakeCurrentUser;... private void SetFakeCurrentUserHierarchyCode (string hierarchyCode ){ _fakeCurrentUser.FindClaim(AbpXClaimTypes.GlobalHierarchyCode).Returns( new Claim(AbpXClaimTypes.GlobalHierarchyCode, hierarchyCode)); }
验证我们之前Seed的三个用户能正确地获取到他们权限范围内的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [Fact ] public async Task Should_Get_Worksheets_For_Current_User ( ){ await WithUnitOfWorkAsync(async () => { SetFakeCurrentUserHierarchyCode(TestAppDataSeedContributor.HierarchyUser1.HierarchyCode); var worksheets = await _worksheetRepository.GetListAsync(); worksheets.Count.ShouldBe(2 ); SetFakeCurrentUserHierarchyCode(TestAppDataSeedContributor.HierarchyUser2.HierarchyCode); worksheets = await _worksheetRepository.GetListAsync(); worksheets.Count.ShouldBe(1 ); worksheets.Single().Id.ShouldBe(TestAppDataSeedContributor.Worksheet1.Id); SetFakeCurrentUserHierarchyCode(TestAppDataSeedContributor.HierarchyUser3.HierarchyCode); worksheets = await _worksheetRepository.GetListAsync(); worksheets.Count.ShouldBe(0 ); }); }
验证未开启数据权限过滤时用户默认应能获取全部数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 [Fact ] public async Task Should_Get_All_Worksheets_When_Filter_Is_Disabled ( ){ await WithUnitOfWorkAsync(async () => { using (_hierarchyDataFilter.Disable()) { SetFakeCurrentUserHierarchyCode(TestAppDataSeedContributor.HierarchyUser3.HierarchyCode); var worksheets = await _worksheetRepository.GetListAsync(); worksheets.Count.ShouldBe(2 ); } }); }
测试写完了,我们来跑一下:
完美通过。
至此数据权限控制模块的开发与测试已全部完毕。