xiangyixiangqin/server/tests/XiangYi.Application.Tests/Services/AdminPermissionPropertyTests.cs
2026-01-02 18:00:49 +08:00

417 lines
19 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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