Files
baiyue-firstleg/iam/src/test/java/com/baiyue/iam/service/RbacServiceTest.java
T
2026-07-31 16:53:07 +08:00

348 lines
14 KiB
Java

package com.baiyue.iam.service;
import com.baiyue.common.core.api.ResultCode;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.iam.dto.RbacDtos.AssignRolesRequest;
import com.baiyue.iam.dto.RbacDtos.GrantPermissionsRequest;
import com.baiyue.iam.dto.RbacDtos.CreateRoleRequest;
import com.baiyue.iam.dto.RbacDtos.UpdateRoleRequest;
import com.baiyue.iam.dto.RbacDtos.ChangeRoleStatusRequest;
import com.baiyue.iam.dto.RbacDtos.CreatePermissionRequest;
import com.baiyue.iam.dto.RbacDtos.UpdatePermissionRequest;
import com.baiyue.iam.dto.RbacDtos.ChangePermissionStatusRequest;
import com.baiyue.iam.entity.IamMembership;
import com.baiyue.iam.entity.IamPermission;
import com.baiyue.iam.entity.IamRole;
import com.baiyue.iam.repository.MemberRepository;
import com.baiyue.iam.repository.RbacRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
@DisplayName("角色与权限服务")
class RbacServiceTest {
@Mock private RbacRepository repository;
@Mock private MemberRepository memberRepository;
private RbacService service;
@BeforeEach
void setUp() {
service = new RbacService();
ReflectionTestUtils.setField(service, "repository", repository);
ReflectionTestUtils.setField(service, "memberRepository", memberRepository);
}
@Test
void roleListReturnsCurrentPermissionsForSafeView() {
IamRole role = role(11L, 100L, "MERCHANT", "CUSTOM");
when(repository.findRoles(100L, "MERCHANT")).thenReturn(List.of(role));
when(repository.findRolePermissionIds(100L, 11L)).thenReturn(Set.of(21L, 22L));
var result = service.roles(100L, "MERCHANT");
assertEquals(Set.of(21L, 22L), result.get(0).permissionIds());
assertEquals(100L, result.get(0).tenantId());
}
@Test
void permissionListPrefersCurrentTenantAndRemovesDuplicateGlobalTemplatesWithSameCode() {
IamPermission global = permission(21L, 0L, "merchant:member:list");
IamPermission tenant = permission(22L, 100L, "merchant:member:list");
IamPermission fallback = permission(23L, 0L, "merchant:member:status");
when(repository.findPermissions(100L, "MERCHANT"))
.thenReturn(List.of(global, fallback, tenant));
var result = service.permissions(100L, "MERCHANT");
assertEquals(2, result.size());
assertEquals(22L, result.get(0).getId());
assertEquals(23L, result.get(1).getId());
}
@Test
void crossTenantRoleAccessIsRejected() {
when(repository.findRole(11L, 200L, "MERCHANT")).thenReturn(null);
BizException exception = assertThrows(BizException.class,
() -> service.role(200L, "MERCHANT", 11L));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
}
@Test
void systemRoleCannotBeDeleted() {
when(repository.findRole(11L, 0L, "PLATFORM"))
.thenReturn(role(11L, 0L, "PLATFORM", "SYSTEM"));
BizException exception = assertThrows(BizException.class,
() -> service.deleteRole(0L, "PLATFORM", 11L));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).deleteRole(anyLong(), any(), anyLong());
}
@Test
void customRoleAssignedToMembersCannotBeDeleted() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.isRoleAssigned(100L, 11L)).thenReturn(true);
BizException exception = assertThrows(BizException.class,
() -> service.deleteRole(100L, "MERCHANT", 11L));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).deleteRole(anyLong(), any(), anyLong());
}
@Test
void emptyPermissionSetExplicitlyClearsAuthorization() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
service.grantPermissions(100L, "MERCHANT", new GrantPermissionsRequest(11L, Set.of()));
verify(repository).replaceRolePermissions(100L, 11L, Set.of());
}
@Test
void emptyRoleSetExplicitlyClearsMemberAuthorization() {
IamMembership membership = new IamMembership();
membership.setId(31L);
membership.setTenantId(100L);
when(memberRepository.findById(31L)).thenReturn(membership);
service.assignRoles(100L, "MERCHANT", new AssignRolesRequest(31L, Set.of()));
verify(repository).replaceMemberRoles(100L, "MERCHANT", 31L, Set.of());
}
@Test
void memberRoleViewReturnsRolesFromCurrentApplicationDomainOnly() {
IamMembership membership = new IamMembership();
membership.setId(31L);
membership.setTenantId(100L);
when(memberRepository.findById(31L)).thenReturn(membership);
when(repository.findMemberRoleIds(100L, 31L)).thenReturn(Set.of(11L, 12L));
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.findRole(12L, 100L, "MERCHANT")).thenReturn(null);
var result = service.memberRoles(100L, "MERCHANT", 31L);
assertEquals(Set.of(11L), result.roleIds());
}
@Test
void duplicateRoleCodeWithinTenantAndApplicationReturnsConflict() {
when(repository.existsRoleCode(100L, "MERCHANT", "ORDER_ADMIN", null)).thenReturn(true);
BizException exception = assertThrows(BizException.class, () -> service.createRole(
100L, "MERCHANT", new CreateRoleRequest("ORDER_ADMIN", "订单管理员", null)));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).insertRole(any());
}
@Test
void disabledRoleCannotBeAssignedToMember() {
IamMembership membership = new IamMembership();
membership.setId(31L);
membership.setTenantId(100L);
when(memberRepository.findById(31L)).thenReturn(membership);
IamRole disabled = role(11L, 100L, "MERCHANT", "CUSTOM");
disabled.setStatus("DISABLED");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(disabled);
BizException exception = assertThrows(BizException.class, () -> service.assignRoles(
100L, "MERCHANT", new AssignRolesRequest(31L, Set.of(11L))));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).replaceMemberRoles(anyLong(), any(), anyLong(), any());
}
@Test
void customRoleCanBeUpdatedAndDisabledWhileRetainingPermissionView() {
IamRole role = role(11L, 100L, "MERCHANT", "CUSTOM");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(role);
when(repository.findRolePermissionIds(100L, 11L)).thenReturn(Set.of(21L));
var updated = service.updateRole(100L, "MERCHANT", 11L,
new UpdateRoleRequest("新名称", "新说明"));
var disabled = service.changeStatus(100L, "MERCHANT", 11L,
new ChangeRoleStatusRequest("DISABLED"));
assertEquals("新名称", updated.roleName());
assertEquals(Set.of(21L), updated.permissionIds());
assertEquals("DISABLED", disabled.status());
verify(repository, times(2)).updateRole(role);
}
@Test
void unreferencedCustomRoleCanBeDeletedSafely() {
when(repository.findRole(11L, 100L, "MERCHANT"))
.thenReturn(role(11L, 100L, "MERCHANT", "CUSTOM"));
when(repository.isRoleAssigned(100L, 11L)).thenReturn(false);
service.deleteRole(100L, "MERCHANT", 11L);
verify(repository).deleteRole(100L, "MERCHANT", 11L);
}
@Test
void tenantOwnerRolesCannotBeChangedThroughRegularMemberAuthorizationEndpoint() {
IamMembership owner = new IamMembership();
owner.setId(31L);
owner.setTenantId(100L);
owner.setMemberType("OWNER");
when(memberRepository.findById(31L)).thenReturn(owner);
BizException exception = assertThrows(BizException.class, () -> service.assignRoles(
100L, "MERCHANT", new AssignRolesRequest(31L, Set.of())));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
verify(repository, never()).replaceMemberRoles(anyLong(), any(), anyLong(), any());
}
@Test
void systemRolePermissionsCannotBeChangedThroughRegularAuthorizationEndpoint() {
when(repository.findRole(11L, 0L, "PLATFORM"))
.thenReturn(role(11L, 0L, "PLATFORM", "SYSTEM"));
BizException exception = assertThrows(BizException.class, () -> service.grantPermissions(
0L, "PLATFORM", new GrantPermissionsRequest(11L, Set.of())));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).replaceRolePermissions(anyLong(), anyLong(), any());
}
@Test
void tenantOwnerSystemRoleCannotBeAssignedToRegularMember() {
IamMembership member = new IamMembership();
member.setId(31L);
member.setTenantId(100L);
member.setMemberType("EMPLOYEE");
when(memberRepository.findById(31L)).thenReturn(member);
IamRole ownerRole = role(11L, 100L, "MERCHANT", "SYSTEM");
ownerRole.setRoleCode("MERCHANT_OWNER");
when(repository.findRole(11L, 100L, "MERCHANT")).thenReturn(ownerRole);
BizException exception = assertThrows(BizException.class, () -> service.assignRoles(
100L, "MERCHANT", new AssignRolesRequest(31L, Set.of(11L))));
assertEquals(ResultCode.FORBIDDEN, exception.getResultCode());
verify(repository, never()).replaceMemberRoles(anyLong(), any(), anyLong(), any());
}
@Test
void duplicatePermissionCodeReturnsStableConflictWithoutDatabaseWrite() {
when(repository.existsPermissionCode(100L, "MERCHANT", "merchant:role:test"))
.thenReturn(true);
CreatePermissionRequest request = new CreatePermissionRequest(
"BUTTON", "merchant:role:test", "测试按钮", 0L, null, null, 1);
BizException exception = assertThrows(BizException.class,
() -> service.createPermission(100L, "MERCHANT", request));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).insertPermission(any());
}
@Test
void builtInSystemPermissionCannotBeUpdatedDisabledOrDeleted() {
IamPermission builtIn = permission(21L, 0L, "platform:permission:list");
builtIn.setAppCode("PLATFORM");
builtIn.setBuiltIn(true);
when(repository.findOwnedPermission(21L, 0L, "PLATFORM")).thenReturn(builtIn);
BizException exception = assertThrows(BizException.class, () -> service.changePermissionStatus(
0L, "PLATFORM", 21L, new ChangePermissionStatusRequest("DISABLED")));
assertEquals(ResultCode.CONFLICT, exception.getResultCode());
verify(repository, never()).updatePermission(any());
verify(repository, never()).deletePermission(any());
}
@Test
void customPermissionCanBeUpdatedAndToggledWithoutChangingCode() {
IamPermission custom = permission(21L, 100L, "merchant:custom:edit");
when(repository.findOwnedPermission(21L, 100L, "MERCHANT")).thenReturn(custom);
var updated = service.updatePermission(100L, "MERCHANT", 21L,
new UpdatePermissionRequest("新名称", 0L, "/custom", "PUT", 10));
var disabled = service.changePermissionStatus(100L, "MERCHANT", 21L,
new ChangePermissionStatusRequest("DISABLED"));
assertEquals("merchant:custom:edit", updated.getPermissionCode());
assertEquals("新名称", updated.getPermissionName());
assertEquals("DISABLED", disabled.getStatus());
verify(repository, times(2)).updatePermission(custom);
}
@Test
void permissionResourceWithChildrenOrRoleBindingsRejectsDeletion() {
IamPermission custom = permission(21L, 100L, "merchant:custom:edit");
when(repository.findOwnedPermission(21L, 100L, "MERCHANT")).thenReturn(custom);
when(repository.hasPermissionChildren(100L, "MERCHANT", 21L)).thenReturn(true);
BizException childFailure = assertThrows(BizException.class,
() -> service.deletePermission(100L, "MERCHANT", 21L));
assertEquals(ResultCode.CONFLICT, childFailure.getResultCode());
when(repository.hasPermissionChildren(100L, "MERCHANT", 21L)).thenReturn(false);
when(repository.isPermissionGranted(100L, 21L)).thenReturn(true);
BizException bindingFailure = assertThrows(BizException.class,
() -> service.deletePermission(100L, "MERCHANT", 21L));
assertEquals(ResultCode.CONFLICT, bindingFailure.getResultCode());
verify(repository, never()).deletePermission(any());
}
@Test
void unreferencedCustomPermissionCanBeDeleted() {
IamPermission custom = permission(21L, 100L, "merchant:custom:edit");
when(repository.findOwnedPermission(21L, 100L, "MERCHANT")).thenReturn(custom);
when(repository.hasPermissionChildren(100L, "MERCHANT", 21L)).thenReturn(false);
when(repository.isPermissionGranted(100L, 21L)).thenReturn(false);
service.deletePermission(100L, "MERCHANT", 21L);
verify(repository).deletePermission(custom);
}
private IamRole role(Long id, Long tenantId, String appCode, String roleType) {
IamRole role = new IamRole();
role.setId(id);
role.setTenantId(tenantId);
role.setAppCode(appCode);
role.setRoleCode("ROLE_" + id);
role.setRoleName("测试角色");
role.setRoleType(roleType);
role.setStatus("ACTIVE");
return role;
}
private IamPermission permission(Long id, Long tenantId, String code) {
IamPermission permission = new IamPermission();
permission.setId(id);
permission.setTenantId(tenantId);
permission.setAppCode("MERCHANT");
permission.setPermissionCode(code);
permission.setPermissionName(code);
permission.setPermissionType("BUTTON");
permission.setStatus("ACTIVE");
return permission;
}
}