417 lines
19 KiB
C#
417 lines
19 KiB
C#
using FsCheck;
|
||
using FsCheck.Xunit;
|
||
using NSubstitute;
|
||
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using XiangYi.Application.Services;
|
||
using XiangYi.Application.Options;
|
||
using XiangYi.Core.Entities.Admin;
|
||
using XiangYi.Core.Interfaces;
|
||
using XiangYi.Infrastructure.Cache;
|
||
|
||
// Alias for Options.Create to avoid namespace conflict
|
||
using OptionsFactory = Microsoft.Extensions.Options.Options;
|
||
|
||
namespace XiangYi.Application.Tests.Services;
|
||
|
||
/// <summary>
|
||
/// AdminPermission属性测试
|
||
/// </summary>
|
||
public class AdminPermissionPropertyTests
|
||
{
|
||
/// <summary>
|
||
/// **Feature: backend-api, Property 21: 管理员权限验证**
|
||
/// **Validates: Requirements 13.4**
|
||
///
|
||
/// *For any* 管理员访问无权限的接口, 应返回403状态码
|
||
///
|
||
/// 此测试验证:当管理员没有所需权限时,GetAdminPermissionsAsync返回的权限列表不包含该权限
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property AdminWithoutPermission_ShouldNotHaveRequiredPermission()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.Default.PositiveInt(),
|
||
adminIdArb =>
|
||
{
|
||
var adminId = (long)adminIdArb.Get;
|
||
var requiredPermission = "user:delete"; // 需要的权限
|
||
|
||
// Arrange - 创建一个没有任何角色的管理员
|
||
var mockAdminUserRepository = Substitute.For<IRepository<AdminUser>>();
|
||
var mockAdminUserRoleRepository = Substitute.For<IRepository<AdminUserRole>>();
|
||
var mockAdminRoleRepository = Substitute.For<IRepository<AdminRole>>();
|
||
var mockAdminRolePermissionRepository = Substitute.For<IRepository<AdminRolePermission>>();
|
||
var mockAdminPermissionRepository = Substitute.For<IRepository<AdminPermission>>();
|
||
var mockAdminRoleMenuRepository = Substitute.For<IRepository<AdminRoleMenu>>();
|
||
var mockAdminMenuRepository = Substitute.For<IRepository<AdminMenu>>();
|
||
var mockCacheService = Substitute.For<ICacheService>();
|
||
var mockLogger = Substitute.For<ILogger<AdminAuthService>>();
|
||
|
||
var jwtOptions = OptionsFactory.Create(new JwtOptions
|
||
{
|
||
Secret = "test-secret-key-that-is-long-enough-for-256-bits",
|
||
Issuer = "test-issuer",
|
||
Audience = "test-audience",
|
||
ExpireMinutes = 60
|
||
});
|
||
|
||
// 管理员没有任何角色
|
||
mockAdminUserRoleRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminUserRole, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminUserRole>()));
|
||
|
||
var service = new AdminAuthService(
|
||
mockAdminUserRepository,
|
||
mockAdminUserRoleRepository,
|
||
mockAdminRoleRepository,
|
||
mockAdminRolePermissionRepository,
|
||
mockAdminPermissionRepository,
|
||
mockAdminRoleMenuRepository,
|
||
mockAdminMenuRepository,
|
||
mockCacheService,
|
||
jwtOptions,
|
||
mockLogger);
|
||
|
||
// Act
|
||
var permissions = service.GetAdminPermissionsAsync(adminId).Result;
|
||
|
||
// Assert - 没有角色的管理员应该没有任何权限
|
||
var hasNoPermissions = permissions.Count == 0;
|
||
var doesNotHaveRequiredPermission = !permissions.Contains(requiredPermission);
|
||
|
||
return hasNoPermissions && doesNotHaveRequiredPermission;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// **Feature: backend-api, Property 21: 管理员权限验证**
|
||
/// **Validates: Requirements 13.4**
|
||
///
|
||
/// 验证:当管理员有特定角色但该角色没有所需权限时,权限列表不包含该权限
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property AdminWithRoleButNoPermission_ShouldNotHaveRequiredPermission()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.Default.PositiveInt(),
|
||
adminIdArb =>
|
||
{
|
||
var adminId = (long)adminIdArb.Get;
|
||
var roleId = adminId + 100;
|
||
var requiredPermission = "user:delete"; // 需要的权限
|
||
var grantedPermission = "user:view"; // 实际拥有的权限
|
||
|
||
// Arrange
|
||
var mockAdminUserRepository = Substitute.For<IRepository<AdminUser>>();
|
||
var mockAdminUserRoleRepository = Substitute.For<IRepository<AdminUserRole>>();
|
||
var mockAdminRoleRepository = Substitute.For<IRepository<AdminRole>>();
|
||
var mockAdminRolePermissionRepository = Substitute.For<IRepository<AdminRolePermission>>();
|
||
var mockAdminPermissionRepository = Substitute.For<IRepository<AdminPermission>>();
|
||
var mockAdminRoleMenuRepository = Substitute.For<IRepository<AdminRoleMenu>>();
|
||
var mockAdminMenuRepository = Substitute.For<IRepository<AdminMenu>>();
|
||
var mockCacheService = Substitute.For<ICacheService>();
|
||
var mockLogger = Substitute.For<ILogger<AdminAuthService>>();
|
||
|
||
var jwtOptions = OptionsFactory.Create(new JwtOptions
|
||
{
|
||
Secret = "test-secret-key-that-is-long-enough-for-256-bits",
|
||
Issuer = "test-issuer",
|
||
Audience = "test-audience",
|
||
ExpireMinutes = 60
|
||
});
|
||
|
||
// 管理员有一个角色
|
||
mockAdminUserRoleRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminUserRole, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminUserRole>
|
||
{
|
||
new AdminUserRole { UserId = adminId, RoleId = roleId }
|
||
}));
|
||
|
||
// 角色有一个权限(但不是所需的权限)
|
||
var permissionId = roleId + 1000;
|
||
mockAdminRolePermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminRolePermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminRolePermission>
|
||
{
|
||
new AdminRolePermission { RoleId = roleId, PermissionId = permissionId }
|
||
}));
|
||
|
||
// 权限详情
|
||
mockAdminPermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminPermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminPermission>
|
||
{
|
||
new AdminPermission { Id = permissionId, PermissionCode = grantedPermission }
|
||
}));
|
||
|
||
var service = new AdminAuthService(
|
||
mockAdminUserRepository,
|
||
mockAdminUserRoleRepository,
|
||
mockAdminRoleRepository,
|
||
mockAdminRolePermissionRepository,
|
||
mockAdminPermissionRepository,
|
||
mockAdminRoleMenuRepository,
|
||
mockAdminMenuRepository,
|
||
mockCacheService,
|
||
jwtOptions,
|
||
mockLogger);
|
||
|
||
// Act
|
||
var permissions = service.GetAdminPermissionsAsync(adminId).Result;
|
||
|
||
// Assert
|
||
// 1. 应该有权限(因为有角色)
|
||
var hasPermissions = permissions.Count > 0;
|
||
|
||
// 2. 应该有被授予的权限
|
||
var hasGrantedPermission = permissions.Contains(grantedPermission);
|
||
|
||
// 3. 不应该有未授予的权限
|
||
var doesNotHaveRequiredPermission = !permissions.Contains(requiredPermission);
|
||
|
||
return hasPermissions && hasGrantedPermission && doesNotHaveRequiredPermission;
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// **Feature: backend-api, Property 21: 管理员权限验证**
|
||
/// **Validates: Requirements 13.4**
|
||
///
|
||
/// 验证:当管理员有所需权限时,权限列表应包含该权限
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property AdminWithPermission_ShouldHaveRequiredPermission()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.Default.PositiveInt(),
|
||
adminIdArb =>
|
||
{
|
||
var adminId = (long)adminIdArb.Get;
|
||
var roleId = adminId + 100;
|
||
var requiredPermission = "user:delete"; // 需要的权限
|
||
|
||
// Arrange
|
||
var mockAdminUserRepository = Substitute.For<IRepository<AdminUser>>();
|
||
var mockAdminUserRoleRepository = Substitute.For<IRepository<AdminUserRole>>();
|
||
var mockAdminRoleRepository = Substitute.For<IRepository<AdminRole>>();
|
||
var mockAdminRolePermissionRepository = Substitute.For<IRepository<AdminRolePermission>>();
|
||
var mockAdminPermissionRepository = Substitute.For<IRepository<AdminPermission>>();
|
||
var mockAdminRoleMenuRepository = Substitute.For<IRepository<AdminRoleMenu>>();
|
||
var mockAdminMenuRepository = Substitute.For<IRepository<AdminMenu>>();
|
||
var mockCacheService = Substitute.For<ICacheService>();
|
||
var mockLogger = Substitute.For<ILogger<AdminAuthService>>();
|
||
|
||
var jwtOptions = OptionsFactory.Create(new JwtOptions
|
||
{
|
||
Secret = "test-secret-key-that-is-long-enough-for-256-bits",
|
||
Issuer = "test-issuer",
|
||
Audience = "test-audience",
|
||
ExpireMinutes = 60
|
||
});
|
||
|
||
// 管理员有一个角色
|
||
mockAdminUserRoleRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminUserRole, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminUserRole>
|
||
{
|
||
new AdminUserRole { UserId = adminId, RoleId = roleId }
|
||
}));
|
||
|
||
// 角色有所需的权限
|
||
var permissionId = roleId + 1000;
|
||
mockAdminRolePermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminRolePermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminRolePermission>
|
||
{
|
||
new AdminRolePermission { RoleId = roleId, PermissionId = permissionId }
|
||
}));
|
||
|
||
// 权限详情
|
||
mockAdminPermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminPermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminPermission>
|
||
{
|
||
new AdminPermission { Id = permissionId, PermissionCode = requiredPermission }
|
||
}));
|
||
|
||
var service = new AdminAuthService(
|
||
mockAdminUserRepository,
|
||
mockAdminUserRoleRepository,
|
||
mockAdminRoleRepository,
|
||
mockAdminRolePermissionRepository,
|
||
mockAdminPermissionRepository,
|
||
mockAdminRoleMenuRepository,
|
||
mockAdminMenuRepository,
|
||
mockCacheService,
|
||
jwtOptions,
|
||
mockLogger);
|
||
|
||
// Act
|
||
var permissions = service.GetAdminPermissionsAsync(adminId).Result;
|
||
|
||
// Assert - 应该有所需的权限
|
||
return permissions.Contains(requiredPermission);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// **Feature: backend-api, Property 21: 管理员权限验证**
|
||
/// **Validates: Requirements 13.4**
|
||
///
|
||
/// 验证:超级管理员(拥有"*"权限)应该可以访问所有资源
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property SuperAdmin_ShouldHaveWildcardPermission()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.Default.PositiveInt(),
|
||
adminIdArb =>
|
||
{
|
||
var adminId = (long)adminIdArb.Get;
|
||
var roleId = adminId + 100;
|
||
var wildcardPermission = "*"; // 超级管理员权限
|
||
|
||
// Arrange
|
||
var mockAdminUserRepository = Substitute.For<IRepository<AdminUser>>();
|
||
var mockAdminUserRoleRepository = Substitute.For<IRepository<AdminUserRole>>();
|
||
var mockAdminRoleRepository = Substitute.For<IRepository<AdminRole>>();
|
||
var mockAdminRolePermissionRepository = Substitute.For<IRepository<AdminRolePermission>>();
|
||
var mockAdminPermissionRepository = Substitute.For<IRepository<AdminPermission>>();
|
||
var mockAdminRoleMenuRepository = Substitute.For<IRepository<AdminRoleMenu>>();
|
||
var mockAdminMenuRepository = Substitute.For<IRepository<AdminMenu>>();
|
||
var mockCacheService = Substitute.For<ICacheService>();
|
||
var mockLogger = Substitute.For<ILogger<AdminAuthService>>();
|
||
|
||
var jwtOptions = OptionsFactory.Create(new JwtOptions
|
||
{
|
||
Secret = "test-secret-key-that-is-long-enough-for-256-bits",
|
||
Issuer = "test-issuer",
|
||
Audience = "test-audience",
|
||
ExpireMinutes = 60
|
||
});
|
||
|
||
// 管理员有超级管理员角色
|
||
mockAdminUserRoleRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminUserRole, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminUserRole>
|
||
{
|
||
new AdminUserRole { UserId = adminId, RoleId = roleId }
|
||
}));
|
||
|
||
// 角色有通配符权限
|
||
var permissionId = roleId + 1000;
|
||
mockAdminRolePermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminRolePermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminRolePermission>
|
||
{
|
||
new AdminRolePermission { RoleId = roleId, PermissionId = permissionId }
|
||
}));
|
||
|
||
// 权限详情 - 通配符权限
|
||
mockAdminPermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminPermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminPermission>
|
||
{
|
||
new AdminPermission { Id = permissionId, PermissionCode = wildcardPermission }
|
||
}));
|
||
|
||
var service = new AdminAuthService(
|
||
mockAdminUserRepository,
|
||
mockAdminUserRoleRepository,
|
||
mockAdminRoleRepository,
|
||
mockAdminRolePermissionRepository,
|
||
mockAdminPermissionRepository,
|
||
mockAdminRoleMenuRepository,
|
||
mockAdminMenuRepository,
|
||
mockCacheService,
|
||
jwtOptions,
|
||
mockLogger);
|
||
|
||
// Act
|
||
var permissions = service.GetAdminPermissionsAsync(adminId).Result;
|
||
|
||
// Assert - 超级管理员应该有通配符权限
|
||
return permissions.Contains(wildcardPermission);
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// **Feature: backend-api, Property 21: 管理员权限验证**
|
||
/// **Validates: Requirements 13.4**
|
||
///
|
||
/// 验证:管理员拥有多个角色时,应该合并所有角色的权限
|
||
/// </summary>
|
||
[Property(MaxTest = 100)]
|
||
public Property AdminWithMultipleRoles_ShouldHaveCombinedPermissions()
|
||
{
|
||
return Prop.ForAll(
|
||
Arb.Default.PositiveInt(),
|
||
adminIdArb =>
|
||
{
|
||
var adminId = (long)adminIdArb.Get;
|
||
var roleId1 = adminId + 100;
|
||
var roleId2 = adminId + 200;
|
||
var permission1 = "user:view";
|
||
var permission2 = "user:edit";
|
||
|
||
// Arrange
|
||
var mockAdminUserRepository = Substitute.For<IRepository<AdminUser>>();
|
||
var mockAdminUserRoleRepository = Substitute.For<IRepository<AdminUserRole>>();
|
||
var mockAdminRoleRepository = Substitute.For<IRepository<AdminRole>>();
|
||
var mockAdminRolePermissionRepository = Substitute.For<IRepository<AdminRolePermission>>();
|
||
var mockAdminPermissionRepository = Substitute.For<IRepository<AdminPermission>>();
|
||
var mockAdminRoleMenuRepository = Substitute.For<IRepository<AdminRoleMenu>>();
|
||
var mockAdminMenuRepository = Substitute.For<IRepository<AdminMenu>>();
|
||
var mockCacheService = Substitute.For<ICacheService>();
|
||
var mockLogger = Substitute.For<ILogger<AdminAuthService>>();
|
||
|
||
var jwtOptions = OptionsFactory.Create(new JwtOptions
|
||
{
|
||
Secret = "test-secret-key-that-is-long-enough-for-256-bits",
|
||
Issuer = "test-issuer",
|
||
Audience = "test-audience",
|
||
ExpireMinutes = 60
|
||
});
|
||
|
||
// 管理员有两个角色
|
||
mockAdminUserRoleRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminUserRole, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminUserRole>
|
||
{
|
||
new AdminUserRole { UserId = adminId, RoleId = roleId1 },
|
||
new AdminUserRole { UserId = adminId, RoleId = roleId2 }
|
||
}));
|
||
|
||
// 两个角色各有一个权限
|
||
var permissionId1 = roleId1 + 1000;
|
||
var permissionId2 = roleId2 + 1000;
|
||
mockAdminRolePermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminRolePermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminRolePermission>
|
||
{
|
||
new AdminRolePermission { RoleId = roleId1, PermissionId = permissionId1 },
|
||
new AdminRolePermission { RoleId = roleId2, PermissionId = permissionId2 }
|
||
}));
|
||
|
||
// 权限详情
|
||
mockAdminPermissionRepository.GetListAsync(Arg.Any<System.Linq.Expressions.Expression<Func<AdminPermission, bool>>>())
|
||
.Returns(Task.FromResult(new List<AdminPermission>
|
||
{
|
||
new AdminPermission { Id = permissionId1, PermissionCode = permission1 },
|
||
new AdminPermission { Id = permissionId2, PermissionCode = permission2 }
|
||
}));
|
||
|
||
var service = new AdminAuthService(
|
||
mockAdminUserRepository,
|
||
mockAdminUserRoleRepository,
|
||
mockAdminRoleRepository,
|
||
mockAdminRolePermissionRepository,
|
||
mockAdminPermissionRepository,
|
||
mockAdminRoleMenuRepository,
|
||
mockAdminMenuRepository,
|
||
mockCacheService,
|
||
jwtOptions,
|
||
mockLogger);
|
||
|
||
// Act
|
||
var permissions = service.GetAdminPermissionsAsync(adminId).Result;
|
||
|
||
// Assert - 应该同时拥有两个角色的权限
|
||
var hasPermission1 = permissions.Contains(permission1);
|
||
var hasPermission2 = permissions.Contains(permission2);
|
||
|
||
return hasPermission1 && hasPermission2;
|
||
});
|
||
}
|
||
}
|