菜单修改优化
This commit is contained in:
+251
@@ -0,0 +1,251 @@
|
||||
package com.nanri.aiimage.modules.permission.controller;
|
||||
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.admin.support.AdminAuthSupport;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.ImageVideoDataPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.ImageVideoDataPermissionUserVo;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
import com.nanri.aiimage.modules.permission.service.PermissionMenuService;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class PermissionMenuControllerTest {
|
||||
|
||||
@Test
|
||||
void effectivePermissionEndpointAllowsAuthenticatedNormalUserToReadSelf() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
AdminUserEntity currentUser = new AdminUserEntity();
|
||||
currentUser.setId(9L);
|
||||
currentUser.setRole("normal");
|
||||
PermissionMenuItemVo item = new PermissionMenuItemVo();
|
||||
item.setId(1L);
|
||||
|
||||
when(authSupport.requireUser(request)).thenReturn(currentUser);
|
||||
when(service.getUserColumnPermissions(currentUser, 9L, "app")).thenReturn(List.of(item));
|
||||
|
||||
var response = controller.getUserColumnPermissions(request, 9L, "app");
|
||||
|
||||
assertThat(response.getData()).extracting(PermissionMenuItemVo::getId).containsExactly(1L);
|
||||
verify(authSupport).requireUser(request);
|
||||
verify(authSupport, never()).requireAdmin(request);
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalProxyAllowsDatabaseBackedNormalUserToReadOwnEffectivePermissions() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "shared-token");
|
||||
AdminUserEntity currentUser = new AdminUserEntity();
|
||||
currentUser.setId(9L);
|
||||
currentUser.setRole("normal");
|
||||
|
||||
when(authSupport.requireUser(request)).thenThrow(new BusinessException(401, "未登录"));
|
||||
when(request.getHeader("X-Internal-Token")).thenReturn("shared-token");
|
||||
when(request.getParameter("operatorId")).thenReturn("9");
|
||||
when(service.requireUserOperator(9L)).thenReturn(currentUser);
|
||||
when(service.getUserColumnPermissions(currentUser, 9L, "app")).thenReturn(List.of());
|
||||
|
||||
controller.getUserColumnPermissions(request, 9L, "app");
|
||||
|
||||
verify(service).requireUserOperator(9L);
|
||||
verify(service, never()).requireAdminOperator(anyLong());
|
||||
verify(service).getUserColumnPermissions(currentUser, 9L, "app");
|
||||
}
|
||||
|
||||
@Test
|
||||
void directIdEndpointStillRequiresAdminForSelf() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
BusinessException authFailure = new BusinessException(403, "需要管理员权限");
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenThrow(authFailure);
|
||||
|
||||
assertThatThrownBy(() -> controller.getUserColumnIds(request, 9L, "app"))
|
||||
.isSameAs(authFailure);
|
||||
verify(authSupport, never()).requireUser(request);
|
||||
verifyNoInteractions(service);
|
||||
}
|
||||
|
||||
@Test
|
||||
void directIdUpdateForwardsMenuTypeScope() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
AdminUserEntity operator = new AdminUserEntity();
|
||||
operator.setId(1L);
|
||||
operator.setRole("super_admin");
|
||||
UserColumnPermissionUpdateRequest body = new UserColumnPermissionUpdateRequest();
|
||||
body.setColumnIds(List.of(12L));
|
||||
when(authSupport.requireAdmin(request)).thenReturn(operator);
|
||||
|
||||
controller.updateUserColumnIds(request, 9L, "app", body);
|
||||
|
||||
verify(service).updateUserColumnPermissions(eq(operator), eq(9L), eq(body), eq("app"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalProxyUsesSharedTokenAndDatabaseBackedOperator() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "shared-token");
|
||||
AdminUserEntity operator = new AdminUserEntity();
|
||||
operator.setId(12L);
|
||||
operator.setRole("admin");
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenThrow(new BusinessException(401, "未登录"));
|
||||
when(request.getHeader("X-Internal-Token")).thenReturn("shared-token");
|
||||
when(request.getParameter("operatorId")).thenReturn("12");
|
||||
when(service.requireAdminOperator(12L)).thenReturn(operator);
|
||||
when(service.list("app")).thenReturn(List.of());
|
||||
|
||||
controller.listMenus(request, "app");
|
||||
|
||||
verify(service).requireAdminOperator(12L);
|
||||
verify(service).list("app");
|
||||
verify(request, never()).getParameter("superAdmin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalProxyRejectsMismatchedSharedToken() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "shared-token");
|
||||
BusinessException authFailure = new BusinessException(401, "未登录");
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenThrow(authFailure);
|
||||
when(request.getHeader("X-Internal-Token")).thenReturn("wrong-token");
|
||||
|
||||
assertThatThrownBy(() -> controller.listMenus(request, "app"))
|
||||
.isSameAs(authFailure);
|
||||
verify(service, never()).requireAdminOperator(anyLong());
|
||||
verify(service, never()).list("app");
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalProxyUsesSharedTokenFileWhenEnvironmentTokenIsUnconfigured(@TempDir Path tempDir) throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "");
|
||||
Path tokenFile = tempDir.resolve("internal-token");
|
||||
Files.writeString(tokenFile, "file-token\n");
|
||||
ReflectionTestUtils.setField(controller, "internalTokenFile", tokenFile.toString());
|
||||
AdminUserEntity operator = new AdminUserEntity();
|
||||
operator.setId(12L);
|
||||
operator.setRole("admin");
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenThrow(new BusinessException(401, "未登录"));
|
||||
when(request.getHeader("X-Internal-Token")).thenReturn("file-token");
|
||||
when(request.getParameter("operatorId")).thenReturn("12");
|
||||
when(service.requireAdminOperator(12L)).thenReturn(operator);
|
||||
when(service.list("admin")).thenReturn(List.of());
|
||||
|
||||
controller.listMenus(request, "admin");
|
||||
|
||||
verify(service).requireAdminOperator(12L);
|
||||
verify(service).list("admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void internalProxyRejectsMissingSharedTokenFileWhenEnvironmentTokenIsUnconfigured(@TempDir Path tempDir) {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "");
|
||||
ReflectionTestUtils.setField(controller, "internalTokenFile", tempDir.resolve("missing-token").toString());
|
||||
BusinessException authFailure = new BusinessException(401, "未登录");
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenThrow(authFailure);
|
||||
when(request.getHeader("X-Internal-Token")).thenReturn("guessed-token");
|
||||
when(request.getRemoteAddr()).thenReturn("127.0.0.1");
|
||||
when(request.getParameter("operatorId")).thenReturn("12");
|
||||
|
||||
assertThatThrownBy(() -> controller.listMenus(request, "admin"))
|
||||
.isSameAs(authFailure);
|
||||
verify(service, never()).requireAdminOperator(anyLong());
|
||||
verify(service, never()).list("admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configuredInternalTokenTakesPrecedenceOverTokenFile(@TempDir Path tempDir) throws Exception {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
Path tokenFile = tempDir.resolve("internal-token");
|
||||
Files.writeString(tokenFile, "file-token\n");
|
||||
ReflectionTestUtils.setField(controller, "internalToken", "configured-token");
|
||||
ReflectionTestUtils.setField(controller, "internalTokenFile", tokenFile.toString());
|
||||
BusinessException authFailure = new BusinessException(401, "未登录");
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenThrow(authFailure);
|
||||
when(request.getHeader("X-Internal-Token")).thenReturn("file-token");
|
||||
when(request.getParameter("operatorId")).thenReturn("12");
|
||||
|
||||
assertThatThrownBy(() -> controller.listMenus(request, "admin"))
|
||||
.isSameAs(authFailure);
|
||||
verify(service, never()).requireAdminOperator(anyLong());
|
||||
verify(service, never()).list("admin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void imageVideoPermissionEndpointsDelegateAuthenticatedOperator() {
|
||||
AdminAuthSupport authSupport = mock(AdminAuthSupport.class);
|
||||
PermissionMenuService service = mock(PermissionMenuService.class);
|
||||
HttpServletRequest request = mock(HttpServletRequest.class);
|
||||
PermissionMenuController controller = new PermissionMenuController(authSupport, service);
|
||||
AdminUserEntity operator = new AdminUserEntity();
|
||||
operator.setId(1L);
|
||||
operator.setRole("super_admin");
|
||||
ImageVideoDataPermissionUserVo permissionUser = new ImageVideoDataPermissionUserVo();
|
||||
permissionUser.setId(20L);
|
||||
ImageVideoDataPermissionUpdateRequest body = new ImageVideoDataPermissionUpdateRequest();
|
||||
body.setUserIds(List.of(20L));
|
||||
|
||||
when(authSupport.requireAdmin(request)).thenReturn(operator);
|
||||
when(service.listImageVideoDataPermissionUsers(operator)).thenReturn(List.of(permissionUser));
|
||||
when(service.updateImageVideoDataPermissionUsers(operator, List.of(20L))).thenReturn(1);
|
||||
|
||||
var listResponse = controller.listImageVideoDataPermissionUsers(request);
|
||||
var updateResponse = controller.updateImageVideoDataPermissionUsers(request, body);
|
||||
|
||||
assertThat(listResponse.getData()).containsExactly(permissionUser);
|
||||
assertThat(updateResponse.getData()).isEqualTo(1);
|
||||
verify(authSupport, times(2)).requireAdmin(request);
|
||||
verify(service).listImageVideoDataPermissionUsers(operator);
|
||||
verify(service).updateImageVideoDataPermissionUsers(operator, List.of(20L));
|
||||
}
|
||||
}
|
||||
+508
@@ -1,20 +1,33 @@
|
||||
package com.nanri.aiimage.modules.permission.service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.nanri.aiimage.common.exception.BusinessException;
|
||||
import com.nanri.aiimage.modules.permission.mapper.AdminUserMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.PermissionMenuMapper;
|
||||
import com.nanri.aiimage.modules.permission.mapper.UserColumnPermissionMapper;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuCreateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.PermissionMenuUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.dto.UserColumnPermissionUpdateRequest;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.AdminUserEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.PermissionMenuEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.entity.UserColumnPermissionEntity;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.ImageVideoDataPermissionUserVo;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.PermissionMenuItemVo;
|
||||
import com.nanri.aiimage.modules.permission.model.vo.UserColumnIdsVo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -61,10 +74,505 @@ class PermissionMenuServiceTest {
|
||||
verify(permissionMapper, times(0)).insert(any(UserColumnPermissionEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void expandsDirectParentGrantToDescendantsWithoutPersistingChildren() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(userMapper.selectById(9L)).thenReturn(user(9L, "normal", 0));
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(9L, 1L)));
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 10),
|
||||
menu(2L, 1L, "app", 11),
|
||||
menu(3L, 2L, "app", 12),
|
||||
menu(4L, null, "app", 13)));
|
||||
|
||||
List<PermissionMenuItemVo> effective = service.getUserColumnPermissions(9L, "app");
|
||||
|
||||
assertThat(effective).extracting(PermissionMenuItemVo::getId)
|
||||
.containsExactly(1L, 2L, 3L);
|
||||
verify(permissionMapper, never()).insert(any(UserColumnPermissionEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void directIdEndpointDoesNotReturnExpandedChildren() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(userMapper.selectById(9L)).thenReturn(user(9L, "normal", 0));
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(9L, 1L)));
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 10),
|
||||
menu(2L, 1L, "app", 11)));
|
||||
|
||||
UserColumnIdsVo direct = service.getUserColumnIds(9L, "app");
|
||||
|
||||
assertThat(direct.getColumnIds()).containsExactly(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void leafOnlyEffectivePermissionIncludesRootKeyWithoutGrantingAncestor() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(userMapper.selectById(9L)).thenReturn(user(9L, "normal", 0));
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(9L, 2L)));
|
||||
PermissionMenuEntity root = menu(1L, null, "app", 1);
|
||||
root.setColumnKey("brand_front_tools");
|
||||
PermissionMenuEntity leaf = menu(2L, 1L, "app", 2);
|
||||
leaf.setColumnKey("brand");
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(root, leaf));
|
||||
|
||||
List<PermissionMenuItemVo> effective = service.getUserColumnPermissions(9L, "app");
|
||||
|
||||
assertThat(effective).extracting(PermissionMenuItemVo::getId).containsExactly(2L);
|
||||
assertThat(effective.getFirst().getRootColumnKey()).isEqualTo("brand_front_tools");
|
||||
}
|
||||
|
||||
@Test
|
||||
void menuCatalogIncludesRootKeyFromLoadedHierarchy() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
PermissionMenuEntity root = menu(1L, null, "app", 1);
|
||||
root.setColumnKey("brand_front_tools");
|
||||
PermissionMenuEntity leaf = menu(2L, 1L, "app", 2);
|
||||
leaf.setColumnKey("brand");
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(root, leaf));
|
||||
|
||||
List<PermissionMenuItemVo> result = service.list("app");
|
||||
|
||||
assertThat(result).extracting(PermissionMenuItemVo::getId).containsExactly(1L, 2L);
|
||||
assertThat(result).extracting(PermissionMenuItemVo::getRootColumnKey)
|
||||
.containsExactly("brand_front_tools", "brand_front_tools");
|
||||
}
|
||||
|
||||
@Test
|
||||
void superAdminDirectIdEndpointReturnsOnlyPersistedIds() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(userMapper.selectById(1L)).thenReturn(user(1L, "super_admin", 1));
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(1L, 2L)));
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 1),
|
||||
menu(2L, 1L, "app", 2)));
|
||||
|
||||
UserColumnIdsVo direct = service.getUserColumnIds(1L, "app");
|
||||
|
||||
assertThat(direct.getColumnIds()).containsExactly(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void superAdminEffectivePermissionsRemainVirtualFullCatalog() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(userMapper.selectById(1L)).thenReturn(user(1L, "super_admin", 1));
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 1),
|
||||
menu(2L, 1L, "app", 2)));
|
||||
|
||||
List<PermissionMenuItemVo> effective = service.getUserColumnPermissions(1L, "app");
|
||||
|
||||
assertThat(effective).extracting(PermissionMenuItemVo::getId).containsExactly(1L, 2L);
|
||||
verify(permissionMapper, never()).selectList(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalUserCanReadOwnEffectivePermissions() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
AdminUserEntity currentUser = user(9L, "normal", 0);
|
||||
when(userMapper.selectById(9L)).thenReturn(currentUser);
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(9L, 1L)));
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 10),
|
||||
menu(2L, 1L, "app", 11)));
|
||||
|
||||
List<PermissionMenuItemVo> effective = service.getUserColumnPermissions(currentUser, 9L, "app");
|
||||
|
||||
assertThat(effective).extracting(PermissionMenuItemVo::getId)
|
||||
.containsExactly(1L, 2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalUserCannotReadAnotherUsersEffectivePermissions() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
AdminUserEntity currentUser = user(9L, "normal", 0);
|
||||
when(userMapper.selectById(10L)).thenReturn(user(10L, "normal", 0));
|
||||
|
||||
assertThatThrownBy(() -> service.getUserColumnPermissions(currentUser, 10L, "app"))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("管理员权限");
|
||||
verify(permissionMapper, never()).selectList(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsParentFromAnotherMenuType() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
PermissionMenuEntity parent = menu(7L, null, "admin", 1);
|
||||
when(menuMapper.selectById(7L)).thenReturn(parent);
|
||||
PermissionMenuCreateRequest request = createRequest(7L, "app");
|
||||
|
||||
assertThatThrownBy(() -> service.create(request))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("同一类型");
|
||||
verify(menuMapper, never()).insert(any(PermissionMenuEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsParentCycleWhenUpdatingMenu() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
PermissionMenuEntity current = menu(1L, null, "app", 1);
|
||||
PermissionMenuEntity parent = menu(2L, 3L, "app", 2);
|
||||
PermissionMenuEntity ancestor = menu(3L, 1L, "app", 3);
|
||||
when(menuMapper.selectById(1L)).thenReturn(current);
|
||||
when(menuMapper.selectById(2L)).thenReturn(parent);
|
||||
when(menuMapper.selectById(3L)).thenReturn(ancestor);
|
||||
|
||||
PermissionMenuUpdateRequest request = new PermissionMenuUpdateRequest();
|
||||
request.setName("changed");
|
||||
request.setColumnKey("changed");
|
||||
request.setMenuType("app");
|
||||
request.setRoutePath("changed");
|
||||
request.setParentId(2L);
|
||||
|
||||
assertThatThrownBy(() -> service.update(1L, request))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("自己的子级");
|
||||
verify(menuMapper, never()).updateById(any(PermissionMenuEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsChangingMenuTypeWhileChildrenRemain() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(menuMapper.selectById(1L)).thenReturn(menu(1L, null, "app", 1));
|
||||
when(menuMapper.selectCount(any())).thenReturn(1L);
|
||||
PermissionMenuUpdateRequest request = new PermissionMenuUpdateRequest();
|
||||
request.setName("changed");
|
||||
request.setColumnKey("changed");
|
||||
request.setMenuType("admin");
|
||||
request.setRoutePath("changed");
|
||||
|
||||
assertThatThrownBy(() -> service.update(1L, request))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("子菜单");
|
||||
verify(menuMapper, never()).updateById(any(PermissionMenuEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void refusesDeletingParentMenuUntilChildrenAreRemoved() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(menuMapper.selectById(1L)).thenReturn(menu(1L, null, "app", 1));
|
||||
when(menuMapper.selectCount(any())).thenReturn(1L);
|
||||
|
||||
assertThatThrownBy(() -> service.delete(1L))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("子菜单");
|
||||
verify(menuMapper, never()).deleteById(any(Long.class));
|
||||
verify(permissionMapper, never()).delete(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordinaryAdminCannotAssignMenuOutsideOwnEffectiveSet() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
AdminUserEntity operator = user(10L, "admin", 1);
|
||||
AdminUserEntity target = user(20L, "normal", 0);
|
||||
target.setCreatedById(10L);
|
||||
when(userMapper.selectById(20L)).thenReturn(target);
|
||||
when(userMapper.selectById(10L)).thenReturn(operator);
|
||||
when(userMapper.selectOne(any())).thenReturn(null);
|
||||
when(menuMapper.selectCount(any())).thenReturn(1L);
|
||||
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(menu(1L, null, "app", 1)));
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(10L, 1L)));
|
||||
|
||||
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||
request.setColumnIds(List.of(2L));
|
||||
|
||||
assertThatThrownBy(() -> service.updateUserColumnPermissions(operator, 20L, request))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("自己已有");
|
||||
verify(permissionMapper, never()).delete(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordinaryAdminCanAssignDescendantGrantedThroughParent() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
AdminUserEntity operator = user(10L, "admin", 1);
|
||||
AdminUserEntity target = user(20L, "normal", 0);
|
||||
target.setCreatedById(10L);
|
||||
when(userMapper.selectById(20L)).thenReturn(target);
|
||||
when(userMapper.selectOne(any())).thenReturn(null);
|
||||
when(menuMapper.selectCount(any())).thenReturn(1L);
|
||||
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 1),
|
||||
menu(2L, 1L, "app", 2)));
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(10L, 1L)));
|
||||
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||
request.setColumnIds(List.of(2L));
|
||||
|
||||
service.updateUserColumnPermissions(operator, 20L, request);
|
||||
|
||||
ArgumentCaptor<UserColumnPermissionEntity> captor = ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||
verify(permissionMapper).insert(captor.capture());
|
||||
assertThat(captor.getValue().getColumnId()).isEqualTo(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ordinaryAdminReplacementPreservesDirectGrantsOutsideEffectiveScope() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
AdminUserEntity operator = user(10L, "admin", 1);
|
||||
AdminUserEntity target = user(20L, "normal", 0);
|
||||
target.setCreatedById(10L);
|
||||
when(userMapper.selectById(20L)).thenReturn(target);
|
||||
when(menuMapper.selectCount(any())).thenReturn(1L);
|
||||
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(1L, null, "app", 1),
|
||||
menu(2L, null, "app", 2)));
|
||||
when(permissionMapper.selectList(any()))
|
||||
.thenReturn(List.of(grant(10L, 1L)), List.of(grant(20L, 2L)));
|
||||
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||
request.setColumnIds(List.of(1L));
|
||||
|
||||
service.updateUserColumnPermissions(operator, 20L, request);
|
||||
|
||||
ArgumentCaptor<UserColumnPermissionEntity> inserted =
|
||||
ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||
verify(permissionMapper, times(2)).insert(inserted.capture());
|
||||
assertThat(inserted.getAllValues())
|
||||
.extracting(UserColumnPermissionEntity::getColumnId)
|
||||
.containsExactly(1L, 2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void replacementStoresOnlyDirectRequestedParentId() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
when(userMapper.selectById(9L)).thenReturn(user(9L, "normal", 0));
|
||||
when(menuMapper.selectCount(any())).thenReturn(1L);
|
||||
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||
request.setColumnIds(List.of(1L));
|
||||
|
||||
service.updateUserColumnPermissions(9L, request);
|
||||
|
||||
ArgumentCaptor<UserColumnPermissionEntity> captor = ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||
verify(permissionMapper).insert(captor.capture());
|
||||
assertThat(captor.getValue().getColumnId()).isEqualTo(1L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void appScopedReplacementDeletesOnlyAppDirectGrants() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
|
||||
AdminUserEntity operator = user(1L, "super_admin", 1);
|
||||
when(userMapper.selectById(9L)).thenReturn(user(9L, "normal", 0));
|
||||
when(menuMapper.selectList(any())).thenReturn(List.of(
|
||||
menu(11L, null, "app", 1),
|
||||
menu(12L, 11L, "app", 2)));
|
||||
when(menuMapper.selectOne(any())).thenReturn(null);
|
||||
UserColumnPermissionUpdateRequest request = new UserColumnPermissionUpdateRequest();
|
||||
request.setColumnIds(List.of(12L));
|
||||
|
||||
service.updateUserColumnPermissions(operator, 9L, request, PermissionMenuService.MENU_TYPE_APP);
|
||||
|
||||
TableInfoHelper.initTableInfo(
|
||||
new MapperBuilderAssistant(new MybatisConfiguration(), ""),
|
||||
UserColumnPermissionEntity.class);
|
||||
ArgumentCaptor<LambdaUpdateWrapper<UserColumnPermissionEntity>> deleted =
|
||||
ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
|
||||
verify(permissionMapper).delete(deleted.capture());
|
||||
assertThat(deleted.getValue().getSqlSegment()).contains("column_id", "IN");
|
||||
assertThat(deleted.getValue().getParamNameValuePairs().values())
|
||||
.contains(9L, 11L, 12L);
|
||||
ArgumentCaptor<UserColumnPermissionEntity> inserted =
|
||||
ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||
verify(permissionMapper).insert(inserted.capture());
|
||||
assertThat(inserted.getValue().getColumnId()).isEqualTo(12L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitLowestIdAdminCannotManageImageVideoDataPermissions() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
AdminUserEntity explicitAdmin = user(1L, "admin", 1);
|
||||
|
||||
assertThatThrownBy(() -> service.listImageVideoDataPermissionUsers(explicitAdmin))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("超级管理员");
|
||||
assertThatThrownBy(() -> service.updateImageVideoDataPermissionUsers(explicitAdmin, List.of(2L)))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("超级管理员");
|
||||
verify(menuMapper, never()).selectOne(any());
|
||||
verify(permissionMapper, never()).delete(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitNormalRoleCannotActAsInternalAdminDespiteLegacyFlag() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
when(userMapper.selectById(1L)).thenReturn(user(1L, "normal", 1));
|
||||
|
||||
assertThatThrownBy(() -> service.requireAdminOperator(1L))
|
||||
.isInstanceOf(BusinessException.class)
|
||||
.hasMessageContaining("管理员权限");
|
||||
}
|
||||
|
||||
@Test
|
||||
void superAdminCanListImageVideoDataPermissionUsers() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
AdminUserEntity operator = user(99L, "super_admin", 1);
|
||||
AdminUserEntity explicitAdmin = user(1L, "admin", 1);
|
||||
explicitAdmin.setUsername("admin");
|
||||
AdminUserEntity normal = user(2L, "normal", 0);
|
||||
normal.setUsername("normal");
|
||||
|
||||
when(menuMapper.selectOne(any())).thenReturn(imageVideoDataPermission());
|
||||
when(permissionMapper.selectList(any())).thenReturn(List.of(grant(1L, 75L)));
|
||||
when(userMapper.selectList(any())).thenReturn(List.of(operator, explicitAdmin, normal));
|
||||
|
||||
List<ImageVideoDataPermissionUserVo> result = service.listImageVideoDataPermissionUsers(operator);
|
||||
|
||||
assertThat(result).extracting(ImageVideoDataPermissionUserVo::getId).containsExactly(1L, 2L);
|
||||
assertThat(result.get(0).isGranted()).isTrue();
|
||||
assertThat(result.get(1).isGranted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void imageVideoUpdateDeletesOnlyProtectedColumnGrants() {
|
||||
PermissionMenuMapper menuMapper = mock(PermissionMenuMapper.class);
|
||||
UserColumnPermissionMapper permissionMapper = mock(UserColumnPermissionMapper.class);
|
||||
AdminUserMapper userMapper = mock(AdminUserMapper.class);
|
||||
PermissionMenuService service = new PermissionMenuService(menuMapper, permissionMapper, userMapper);
|
||||
AdminUserEntity operator = user(99L, "super_admin", 1);
|
||||
AdminUserEntity explicitAdmin = user(1L, "admin", 1);
|
||||
AdminUserEntity normal = user(2L, "normal", 0);
|
||||
|
||||
when(menuMapper.selectOne(any())).thenReturn(imageVideoDataPermission());
|
||||
when(userMapper.selectList(any())).thenReturn(List.of(operator, explicitAdmin, normal));
|
||||
|
||||
int grantedCount = service.updateImageVideoDataPermissionUsers(operator, List.of(2L));
|
||||
|
||||
assertThat(grantedCount).isEqualTo(1);
|
||||
verify(permissionMapper).deleteByMap(Map.of("column_id", 75L));
|
||||
|
||||
ArgumentCaptor<UserColumnPermissionEntity> inserted =
|
||||
ArgumentCaptor.forClass(UserColumnPermissionEntity.class);
|
||||
verify(permissionMapper).insert(inserted.capture());
|
||||
assertThat(inserted.getValue().getUserId()).isEqualTo(2L);
|
||||
assertThat(inserted.getValue().getColumnId()).isEqualTo(75L);
|
||||
}
|
||||
|
||||
private PermissionMenuCreateRequest createRequest(Long parentId, String menuType) {
|
||||
PermissionMenuCreateRequest request = new PermissionMenuCreateRequest();
|
||||
request.setName("child");
|
||||
request.setColumnKey("child");
|
||||
request.setMenuType(menuType);
|
||||
request.setRoutePath("child");
|
||||
request.setParentId(parentId);
|
||||
return request;
|
||||
}
|
||||
|
||||
private PermissionMenuEntity menu(Long id, Long parentId, String menuType, int sortOrder) {
|
||||
PermissionMenuEntity entity = new PermissionMenuEntity();
|
||||
entity.setId(id);
|
||||
entity.setParentId(parentId);
|
||||
entity.setMenuType(menuType);
|
||||
entity.setColumnKey("menu-" + id);
|
||||
entity.setRoutePath("route-" + id);
|
||||
entity.setSortOrder(sortOrder);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private UserColumnPermissionEntity grant(Long userId, Long columnId) {
|
||||
UserColumnPermissionEntity entity = new UserColumnPermissionEntity();
|
||||
entity.setUserId(userId);
|
||||
entity.setColumnId(columnId);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private AdminUserEntity user(Long id, String role, int isAdmin) {
|
||||
AdminUserEntity entity = new AdminUserEntity();
|
||||
entity.setId(id);
|
||||
entity.setRole(role);
|
||||
entity.setIsAdmin(isAdmin);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private PermissionMenuEntity imageVideoPermission() {
|
||||
PermissionMenuEntity entity = new PermissionMenuEntity();
|
||||
entity.setId(75L);
|
||||
entity.setColumnKey("admin_image_video_tasks");
|
||||
return entity;
|
||||
}
|
||||
|
||||
private PermissionMenuEntity imageVideoDataPermission() {
|
||||
PermissionMenuEntity entity = new PermissionMenuEntity();
|
||||
entity.setId(75L);
|
||||
entity.setColumnKey("admin_image_video_task_data");
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user