0%

基于Abp的扩展模块实现第1例 - 数据权限控制(测试)

其他系列博文

背景

在上一篇博文中,我们通过使用物化路径模型实现了一个基于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. 在测试文件中注入全局层级管理器:
1
2
3
4
5
6
private readonly IHierarchyCodeManager<IHasHierarchy> _globalManager;

public GlobalHierarchyCodeManager_Tests()
{
_globalManager = GetRequiredService<IHierarchyCodeManager<IHasHierarchy>>();
}
  1. 测试默认注册的IHierarchyCodeManager<IHasHierarchy>为我们需要的GlobalHierarchyCodeManager
1
2
3
4
5
[Fact]
public void Should_Register_Global_Code_Manager_As_Default()
{
ProxyHelper.UnProxy(_globalManager).ShouldBeOfType<GlobalHierarchyCodeManager>();
}
  1. 测试层级树中可包含不同节点类型:
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的编码风格进行。

创建两个测试领域实体HierarchicalUserWorksheet,前者代表真实应用中我们的用户类,后者代表真实应用中具有获取权限的数据:

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. 验证一下权限数据过滤器已开启:

    1
    2
    3
    4
    5
    6
    [Fact]
    public void Should_Enabled_UseHierarchyUser_Option()
    {
    var option = GetRequiredService<IOptions<AbpXSecurityOptions>>().Value;
    option.UseHierarchyUser.ShouldBeTrue();
    }
  2. 为了便于测试,我们不引入真实的用户身份信息验证流程,直接在测试类中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));
    }
  3. 验证我们之前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);
    });
    }
  4. 验证未开启数据权限过滤时用户默认应能获取全部数据:

    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);
    }
    });
    }

测试写完了,我们来跑一下:

测试结果

完美通过。

至此数据权限控制模块的开发与测试已全部完毕。