diff --git a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/test/WithMockLoginUser.java b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/test/WithMockLoginUser.java index 19ca12650..ac9d12a76 100644 --- a/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/test/WithMockLoginUser.java +++ b/cf-framework/cf-spring-boot-starter-security/src/main/java/com/cf/imes/framework/security/test/WithMockLoginUser.java @@ -23,4 +23,8 @@ public @interface WithMockLoginUser { boolean isSuperAdmin() default false; long organId() default 0L; + + int userType() default 1; + + long productId() default 0L; } \ No newline at end of file diff --git a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/executor/config/ExecutorThreadPoolConfiguration.java b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/executor/config/ExecutorThreadPoolConfiguration.java index 545ee6e63..9ace0c0dc 100644 --- a/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/executor/config/ExecutorThreadPoolConfiguration.java +++ b/cf-module-prod-executor/cf-module-prod-executor-biz/src/main/java/com/cf/imes/module/executor/framework/executor/config/ExecutorThreadPoolConfiguration.java @@ -12,9 +12,9 @@ import java.util.concurrent.ThreadPoolExecutor; */ @Configuration(proxyBeanMethods = false) public class ExecutorThreadPoolConfiguration { - public static final String EXECUTOR_IMPOT_THREAD_POOL_TASK_EXECUTOR = "EXECUTOR_IMPORT_THREAD_POOL_TASK_EXECUTOR"; + public static final String EXECUTOR_IMPOT_THREAD_POOL_TASK_EXECUTOR = "executorImportThreadPoolTaskExecutor"; - @Bean(EXECUTOR_IMPOT_THREAD_POOL_TASK_EXECUTOR) + @Bean("executorImportThreadPoolTaskExecutor") public ThreadPoolTaskExecutor notifyThreadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(8); // 设置核心线程数 diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/executor/config/SystemThreadPoolConfiguration.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/executor/config/SystemThreadPoolConfiguration.java new file mode 100644 index 000000000..8556a6306 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/framework/executor/config/SystemThreadPoolConfiguration.java @@ -0,0 +1,32 @@ +package com.cf.imes.module.system.framework.executor.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +/** + * system服务线程池配置 + * + * @author Gqr + * @since 2026/1/26 14:29 + */ +@Configuration(proxyBeanMethods = false) +public class SystemThreadPoolConfiguration { + public static final String SYSTEM_PERMISSION_THREAD_POOL_TASK_EXECUTOR = "systemPermissionThreadPoolTaskExecutor"; + + @Bean("systemPermissionThreadPoolTaskExecutor") + public ThreadPoolTaskExecutor systemPermissionThreadPoolTaskExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(8); // 设置核心线程数 + executor.setMaxPoolSize(8); // 设置最大线程数 + executor.setKeepAliveSeconds(60); // 设置空闲时间 + executor.setQueueCapacity(100); // 设置队列大小 + executor.setThreadNamePrefix("system-permission-Executor-"); // 配置线程池的前缀 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + // 进行加载 + executor.initialize(); + return executor; + } +} diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java index e368ec0e3..ca5d4d728 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/permission/MenuServiceImpl.java @@ -36,6 +36,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ScanOptions; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -53,6 +54,7 @@ import java.util.stream.IntStream; import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList; import static com.cf.imes.module.system.enums.ErrorCodeConstants.*; +import static com.cf.imes.module.system.framework.executor.config.SystemThreadPoolConfiguration.SYSTEM_PERMISSION_THREAD_POOL_TASK_EXECUTOR; /** * 菜单 Service 实现 @@ -80,6 +82,8 @@ public class MenuServiceImpl implements MenuService { @Resource private TransProperties transProperties; + @Resource(name = SYSTEM_PERMISSION_THREAD_POOL_TASK_EXECUTOR) + private ThreadPoolTaskExecutor threadPoolTaskExecutor; private static final String FIELD_CHARACTER = ":*:%s"; @@ -167,7 +171,7 @@ public class MenuServiceImpl implements MenuService { * @param sourceType */ private void validateManageSourceTypeOperatePermission(Integer sourceType) { - if (!SecurityFrameworkUtils.isCfOrg() && sourceType.equals(MenuSourceTypeEnum.MANAGEMENT.getType())) { + if (!SecurityFrameworkUtils.isCfOrg() && MenuSourceTypeEnum.MANAGEMENT.getType().equals(sourceType)) { throw new ServiceException(MANAGEMENT_MENU_OPERATION_PERMISSION_ERROR); } } @@ -181,33 +185,32 @@ public class MenuServiceImpl implements MenuService { */ private void flushCacheWhenOperateMenu(Long menuId, String newPermission, String oldPermission) { CompletableFuture.runAsync(() -> { - if (ObjectUtil.isNotNull(oldPermission)) { - // 移除 USER_ROLE_ID_LIST:*:旧的permission - String oldMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(FIELD_CHARACTER)), oldPermission); - Long oldBatchDelPerNum = scanAndDelKeys(oldMenuPerPattern); - log.info("[updateMenu] 批量删除redis[{}]数量:{}", oldMenuPerPattern, oldBatchDelPerNum); - } + try { + if (ObjectUtil.isNotNull(oldPermission)) { + // 移除 USER_ROLE_ID_LIST:*:旧的permission + String oldMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(FIELD_CHARACTER)), oldPermission); + Long oldBatchDelPerNum = scanAndDelKeys(oldMenuPerPattern); + log.info("[updateMenu] 批量删除redis[{}]数量:{}", oldMenuPerPattern, oldBatchDelPerNum); + } - if (ObjectUtil.isNotNull(newPermission)) { - // 移除 USER_ROLE_ID_LIST:*:新的permission - String newMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(FIELD_CHARACTER)), newPermission); - Long newBatchDelPerNum = scanAndDelKeys(newMenuPerPattern); - log.info("[updateMenu] 批量删除redis[{}]数量:{}", newMenuPerPattern, newBatchDelPerNum); - } + if (ObjectUtil.isNotNull(newPermission)) { + // 移除 USER_ROLE_ID_LIST:*:新的permission + String newMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(FIELD_CHARACTER)), newPermission); + Long newBatchDelPerNum = scanAndDelKeys(newMenuPerPattern); + log.info("[updateMenu] 批量删除redis[{}]数量:{}", newMenuPerPattern, newBatchDelPerNum); + } - if (ObjectUtil.isNotNull(menuId)) { // 移除 MENU_ROLE_ID_LIST:*:menuId String menuRolePattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.MENU_ROLE_ID_LIST).append(FIELD_CHARACTER)), menuId); Long batchDelRoleNum = scanAndDelKeys(menuRolePattern); log.info("[deleteMenu] 批量删除redis[{}]数量:{}", menuRolePattern, batchDelRoleNum); - } - // 清空本地缓存 - redisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, ""); - }).exceptionally(e -> { - log.error("[deleteMenu] redis 清空PERMISSION_MENU_ID_LIST或message发送失败, topic:{}, 异常:{}", com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, e); - return null; - }); + // 清空本地缓存 + redisTemplate.convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, ""); + } catch (Exception e) { + log.error("[deleteMenu] redis 清空PERMISSION_MENU_ID_LIST或message发送失败, topic:{}, 异常:{}", com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, e); + } + }, threadPoolTaskExecutor); } @@ -263,7 +266,7 @@ public class MenuServiceImpl implements MenuService { @Override public List getMenuList(Collection ids) { - return menuMapper.selectBatchIds(ids); + return menuMapper.selectByIds(ids); } @Override @@ -272,7 +275,7 @@ public class MenuServiceImpl implements MenuService { if(CollUtil.isEmpty(ids)) { return new ArrayList<>(); } - return menuMapper.selectBatchIds(ids); + return menuMapper.selectByIds(ids); } /** @@ -300,8 +303,7 @@ public class MenuServiceImpl implements MenuService { throw new ServiceException(MENU_PARENT_NOT_EXISTS); } // 父菜单必须是目录或者菜单类型 - if (!MenuTypeEnum.DIR.getType().equals(menu.getType()) - && !MenuTypeEnum.MENU.getType().equals(menu.getType())) { + if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) { throw new ServiceException(MENU_PARENT_NOT_DIR_OR_MENU); } } @@ -407,7 +409,7 @@ public class MenuServiceImpl implements MenuService { String src = item.getString("src"); String dst = item.getString("dst"); - if (menuDO == null || !menuDO.getName().equals(UnicodeUtil.toString(src))) { + if (!menuDO.getName().equals(UnicodeUtil.toString(src))) { continue; } diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/common/WithMockLoginUserExtension.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/common/WithMockLoginUserExtension.java index ec82835d9..d0bd3c04d 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/common/WithMockLoginUserExtension.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/common/WithMockLoginUserExtension.java @@ -2,6 +2,7 @@ package com.cf.imes.module.system.common; import com.cf.imes.framework.security.core.LoginUser; import com.cf.imes.framework.security.test.WithMockLoginUser; +import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.extension.AfterEachCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -16,6 +17,7 @@ import org.springframework.security.core.context.SecurityContextHolder; * @author Gqr * @since 2026/1/16 10:20 */ +@Slf4j public class WithMockLoginUserExtension implements BeforeEachCallback, AfterEachCallback { @Override public void afterEach(ExtensionContext context) { @@ -33,6 +35,8 @@ public class WithMockLoginUserExtension implements BeforeEachCallback, AfterEach loginUser.setDeptId(ann.deptId()); loginUser.setNickname(ann.username()); loginUser.setIsSupAdmin(ann.isSuperAdmin()); + loginUser.setUserType(ann.userType()); + loginUser.setProductId(ann.productId()); } else { return; } diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java index 9b8ae2285..ad4baa8aa 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/auth/AuthControllerTest.java @@ -6,10 +6,12 @@ import com.cf.imes.framework.ip.core.service.dto.IPQueryDataRespDTO; import com.cf.imes.framework.security.config.SecurityProperties; import com.cf.imes.framework.security.core.LoginUser; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; import com.cf.imes.framework.web.config.ChenfengWebAutoConfiguration; import com.cf.imes.module.infra.api.logger.ApiErrorLogApi; import com.cf.imes.module.system.api.sms.dto.code.SmsCodeSendReqDTO; +import com.cf.imes.module.system.common.WithMockLoginUserExtension; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginReqVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginRespVO; import com.cf.imes.module.system.controller.admin.auth.vo.AuthLoginSmsCheckReqVO; @@ -31,6 +33,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.MockedStatic; import org.springframework.beans.factory.annotation.Autowired; @@ -74,6 +77,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. */ @WebMvcTest(controllers = AuthController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) @Import(ChenfengWebAutoConfiguration.class) +@ExtendWith(WithMockLoginUserExtension.class) @TestPropertySource(properties = { "spring.application.name=test-app" }) @@ -283,136 +287,138 @@ class AuthControllerTest extends BaseWebUnitTest { } @Test + @WithMockLoginUser(userId = 1L, organId = 100L) void testGetPermissionInfo_success() throws Exception { - try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { - utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); - utilities.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(false); + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setOrganId(100L); + when(userService.getUser(1L)).thenReturn(user); - AdminUserDO user = new AdminUserDO(); - user.setId(1L); - user.setOrganId(100L); - when(userService.getUser(1L)).thenReturn(user); + OrganizationDO organization = new OrganizationDO(); + when(organService.validOrgan(100L)).thenReturn(organization); - OrganizationDO organization = new OrganizationDO(); - when(organService.validOrgan(100L)).thenReturn(organization); + when(ipQueryService.serviceEnable()).thenReturn(false); - when(ipQueryService.serviceEnable()).thenReturn(false); + Set roleIds = new HashSet<>(Arrays.asList(10L, 20L)); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); - Set roleIds = new HashSet<>(Arrays.asList(10L, 20L)); - when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + RoleDO role1 = new RoleDO(); + role1.setId(10L); + role1.setStatus(1); + RoleDO role2 = new RoleDO(); + role2.setId(20L); + role2.setStatus(1); + when(roleService.getRoleList1(roleIds)) + .thenReturn(new ArrayList<>(Arrays.asList(role1, role2))); - RoleDO role1 = new RoleDO(); role1.setId(10L); role1.setStatus(1); - RoleDO role2 = new RoleDO(); role2.setId(20L); role2.setStatus(1); - when(roleService.getRoleList1(roleIds)) - .thenReturn(new ArrayList<>(Arrays.asList(role1, role2))); + Set menuIds = new HashSet<>(Arrays.asList(1000L, 2000L)); + when(permissionService.getRoleMenuListByRoleId2(anySet(), eq(100L))).thenReturn(menuIds); - Set menuIds = new HashSet<>(Arrays.asList(1000L, 2000L)); - when(permissionService.getRoleMenuListByRoleId2(anySet(), eq(100L))).thenReturn(menuIds); + MenuDO menu1 = new MenuDO(); + menu1.setId(1000L); + menu1.setStatus(1); + MenuDO menu2 = new MenuDO(); + menu2.setId(2000L); + menu2.setStatus(1); + when(menuService.getCustomEndPointMenuList(menuIds)) + .thenReturn(new ArrayList<>(Arrays.asList(menu1, menu2))); - MenuDO menu1 = new MenuDO(); menu1.setId(1000L); menu1.setStatus(1); - MenuDO menu2 = new MenuDO(); menu2.setId(2000L); menu2.setStatus(1); - when(menuService.getCustomEndPointMenuList(menuIds)) - .thenReturn(new ArrayList<>(Arrays.asList(menu1, menu2))); + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").exists()); - mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data").exists()); - - // 验证调用顺序/次数(可选) - verify(userService, times(1)).getUser(1L); - verify(organService, times(1)).validOrgan(100L); - verify(roleService, times(1)).getRoleList1(roleIds); - verify(menuService, times(1)).getCustomEndPointMenuList(menuIds); - } + // 验证调用顺序/次数(可选) + verify(userService, times(1)).getUser(1L); + verify(organService, times(1)).validOrgan(100L); + verify(roleService, times(1)).getRoleList1(roleIds); + verify(menuService, times(1)).getCustomEndPointMenuList(menuIds); } @Test + @WithMockLoginUser(userId = 1L, organId = 100L, userType = 2) void testGetPermissionInfo_success_branch() throws Exception { - try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { - utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); - utilities.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(true); + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setOrganId(100L); + when(userService.getUser(1L)).thenReturn(user); - AdminUserDO user = new AdminUserDO(); - user.setId(1L); - user.setOrganId(100L); - when(userService.getUser(1L)).thenReturn(user); + OrganizationDO organization = new OrganizationDO(); + when(organService.validOrgan(100L)).thenReturn(organization); - OrganizationDO organization = new OrganizationDO(); - when(organService.validOrgan(100L)).thenReturn(organization); + IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO(); + ipResp.setProv("浙江"); + ipResp.setCity("杭州"); + ipResp.setArea("西湖"); + when(ipQueryService.serviceEnable()).thenReturn(true); + when(ipQueryService.querySource(anyString())).thenReturn(ipResp); - IPQueryDataRespDTO ipResp = new IPQueryDataRespDTO(); - ipResp.setProv("浙江"); - ipResp.setCity("杭州"); - ipResp.setArea("西湖"); - when(ipQueryService.serviceEnable()).thenReturn(true); - when(ipQueryService.querySource(anyString())).thenReturn(ipResp); + Set roleIds = new HashSet<>(Arrays.asList(10L, 20L)); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); - Set roleIds = new HashSet<>(Arrays.asList(10L, 20L)); - when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + RoleDO role1 = new RoleDO(); + role1.setId(10L); + role1.setStatus(1); + RoleDO role2 = new RoleDO(); + role2.setId(20L); + role2.setStatus(1); + when(roleService.getRoleList1(roleIds)) + .thenReturn(new ArrayList<>(Arrays.asList(role1, role2))); - RoleDO role1 = new RoleDO(); role1.setId(10L); role1.setStatus(1); - RoleDO role2 = new RoleDO(); role2.setId(20L); role2.setStatus(1); - when(roleService.getRoleList1(roleIds)) - .thenReturn(new ArrayList<>(Arrays.asList(role1, role2))); + Set menuIds = new HashSet<>(Arrays.asList(1000L, 2000L)); + when(permissionService.getRoleMenuListByRoleId2(anySet(), eq(100L))).thenReturn(menuIds); - Set menuIds = new HashSet<>(Arrays.asList(1000L, 2000L)); - when(permissionService.getRoleMenuListByRoleId2(anySet(), eq(100L))).thenReturn(menuIds); + MenuDO menu1 = new MenuDO(); + menu1.setId(1000L); + menu1.setStatus(1); + MenuDO menu2 = new MenuDO(); + menu2.setId(2000L); + menu2.setStatus(1); + when(menuService.getManageEndPointMenuList(menuIds)) + .thenReturn(new ArrayList<>(Arrays.asList(menu1, menu2))); - MenuDO menu1 = new MenuDO(); menu1.setId(1000L); menu1.setStatus(1); - MenuDO menu2 = new MenuDO(); menu2.setId(2000L); menu2.setStatus(1); - when(menuService.getManageEndPointMenuList(menuIds)) - .thenReturn(new ArrayList<>(Arrays.asList(menu1, menu2))); + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").exists()); - mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data").exists()); - - // 验证调用顺序/次数(可选) - verify(userService, times(1)).getUser(1L); - verify(organService, times(1)).validOrgan(100L); - verify(ipQueryService, times(1)).querySource(anyString()); - verify(roleService, times(1)).getRoleList1(roleIds); - verify(menuService, times(1)).getManageEndPointMenuList(menuIds); - } + // 验证调用顺序/次数(可选) + verify(userService, times(1)).getUser(1L); + verify(organService, times(1)).validOrgan(100L); + verify(ipQueryService, times(1)).querySource(anyString()); + verify(roleService, times(1)).getRoleList1(roleIds); + verify(menuService, times(1)).getManageEndPointMenuList(menuIds); } @Test + @WithMockLoginUser(userId = 1L, organId = 100L) void testGetPermissionInfo_success_roleEmpty() throws Exception { - try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { - utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); - utilities.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(true); + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + user.setOrganId(100L); + when(userService.getUser(1L)).thenReturn(user); - AdminUserDO user = new AdminUserDO(); - user.setId(1L); - user.setOrganId(100L); - when(userService.getUser(1L)).thenReturn(user); + OrganizationDO organization = new OrganizationDO(); + when(organService.validOrgan(100L)).thenReturn(organization); - OrganizationDO organization = new OrganizationDO(); - when(organService.validOrgan(100L)).thenReturn(organization); + when(ipQueryService.serviceEnable()).thenReturn(false); - when(ipQueryService.serviceEnable()).thenReturn(false); + Set roleIds = new HashSet<>(); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); - Set roleIds = new HashSet<>(); - when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.roles").isEmpty()); - mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.roles").isEmpty()); - - // 验证调用顺序/次数(可选) - verify(userService, times(1)).getUser(1L); - verify(organService, times(1)).validOrgan(100L); - verify(ipQueryService, never()).querySource(anyString()); - verify(roleService, never()).getRoleList1(roleIds); - verify(menuService, never()).getManageEndPointMenuList(anySet()); - } + // 验证调用顺序/次数(可选) + verify(userService, times(1)).getUser(1L); + verify(organService, times(1)).validOrgan(100L); + verify(ipQueryService, never()).querySource(anyString()); + verify(roleService, never()).getRoleList1(roleIds); + verify(menuService, never()).getManageEndPointMenuList(anySet()); } @Test + @WithMockLoginUser(userId = 1L) void testGetPermissionInfo_userNull() throws Exception { - try (MockedStatic utilities = mockStatic(SecurityFrameworkUtils.class)) { - utilities.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser); - when(userService.getUser(1L)).thenReturn(null); mockMvc.perform(get("/admin-api/system/auth/get-permission-info")) @@ -420,7 +426,6 @@ class AuthControllerTest extends BaseWebUnitTest { .andExpect(jsonPath("$.data").isEmpty()); // 返回 null verify(userService, times(1)).getUser(1L); - } } @Test diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java index e4cd85c7b..43e6a1ea2 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictDataControllerTest.java @@ -2,6 +2,7 @@ package com.cf.imes.module.system.controller.admin.dict; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO; import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO; import com.cf.imes.module.system.controller.admin.dict.vo.data.DictDataTransReqVO; @@ -37,7 +38,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @since 2026/1/14 15:38 */ @WebMvcTest(controllers = DictDataController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) -class DictDataControllerTest { +class DictDataControllerTest extends BaseWebUnitTest { @Autowired private MockMvc mockMvc; diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictTypeControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictTypeControllerTest.java index 355be534d..b821c0cc9 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictTypeControllerTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/dict/DictTypeControllerTest.java @@ -3,6 +3,7 @@ package com.cf.imes.module.system.controller.admin.dict; import com.cf.imes.framework.common.enums.CommonStatusEnum; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.excel.core.util.ExcelUtils; +import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; import com.cf.imes.module.system.controller.admin.dict.vo.type.DictTypeRespVO; import com.cf.imes.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO; import com.cf.imes.module.system.dal.dataobject.dict.DictTypeDO; @@ -42,7 +43,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @since 2026/1/14 17:25 */ @WebMvcTest(controllers = DictTypeController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) -public class DictTypeControllerTest { +class DictTypeControllerTest extends BaseWebUnitTest { @Autowired private MockMvc mockMvc; diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java index a99b47130..dcb2e2404 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsControllerTest.java @@ -3,7 +3,9 @@ package com.cf.imes.module.system.controller.admin.funds.products; import com.cf.imes.framework.common.pojo.PageParam; import com.cf.imes.framework.common.pojo.PageResult; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; +import com.cf.imes.module.system.common.WithMockLoginUserExtension; import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductPageReqVO; import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductSaveReqVO; import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO; @@ -11,6 +13,7 @@ import com.cf.imes.module.system.enums.products.ProductStatusEnum; import com.cf.imes.module.system.service.funds.products.ProductsService; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -41,6 +44,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. * @since 2025/10/14 11:16 */ @WebMvcTest(controllers = ProductsController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +@ExtendWith(WithMockLoginUserExtension.class) class ProductsControllerTest extends BaseWebUnitTest { @Autowired private MockMvc mockMvc; @@ -164,6 +168,7 @@ class ProductsControllerTest extends BaseWebUnitTest { } @Test + @WithMockLoginUser(userType = 2) void testGetAvailableProductsPage() throws Exception { ProductPageReqVO reqVO = new ProductPageReqVO(); PageResult mockPage = new PageResult<>(); @@ -171,21 +176,17 @@ class ProductsControllerTest extends BaseWebUnitTest { mockPage.setList(List.of(new ProductsDO().setId(1L).setProductName("已上架产品"))); when(productsService.getPage(any())).thenReturn(mockPage); - // mock 管理端标识 - try (var ignored = mockStatic(SecurityFrameworkUtils.class)) { - when(SecurityFrameworkUtils.isManageEndPoint()).thenReturn(true); + mockMvc.perform(get("/system/products/available") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.list[0].productName").value("已上架产品")); - mockMvc.perform(get("/system/products/available") - .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(reqVO))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.list[0].productName").value("已上架产品")); - - verify(productsService, times(1)).getPage(any()); - } + verify(productsService, times(1)).getPage(any()); } @Test + @WithMockLoginUser(productId = 123L) void testGetAvailableProductsPage_nonManager() throws Exception { ProductPageReqVO reqVO = new ProductPageReqVO(); diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseControllerTest.java index 616875a24..317625cbe 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseControllerTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/funds/purchase/PurchaseControllerTest.java @@ -1,15 +1,15 @@ package com.cf.imes.module.system.controller.admin.funds.purchase; import com.cf.imes.framework.common.pojo.PageResult; -import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.framework.security.test.WithMockLoginUser; import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; +import com.cf.imes.module.system.common.WithMockLoginUserExtension; import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordPageReqVO; import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordRespVO; import com.cf.imes.module.system.service.funds.purchase.PurchaseService; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -39,6 +39,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. controllers = PurchaseController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class ) +@ExtendWith(WithMockLoginUserExtension.class) class PurchaseControllerTest extends BaseWebUnitTest { @Autowired private MockMvc mockMvc; @@ -50,13 +51,8 @@ class PurchaseControllerTest extends BaseWebUnitTest { * 非管理端:获取购买记录分页 */ @Test + @WithMockLoginUser(organId = 1L) void testGetPurchaseRecord_nonManage() throws Exception { - try (MockedStatic mocked = Mockito.mockStatic(SecurityFrameworkUtils.class)) { - - // mock 当前为非管理端 - mocked.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(false); - mocked.when(SecurityFrameworkUtils::getUserOrganId).thenReturn(1L); - // mock service 返回 PurchaseRecordRespVO respVO = new PurchaseRecordRespVO(); respVO.setId(1L); @@ -84,18 +80,15 @@ class PurchaseControllerTest extends BaseWebUnitTest { .getPurchaseRecord(captor.capture()); assertEquals(1L, captor.getValue().getOrganId()); - } + } /** * 管理端:获取购买记录分页 */ @Test + @WithMockLoginUser(userType = 2) void testGetPurchaseRecord_manage() throws Exception { - try (MockedStatic mocked = Mockito.mockStatic(SecurityFrameworkUtils.class)) { - - mocked.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(true); - PageResult pageResult = new PageResult<>(); pageResult.setTotal(0L); pageResult.setList(Collections.emptyList()); @@ -110,64 +103,54 @@ class PurchaseControllerTest extends BaseWebUnitTest { verify(purchaseService, times(1)) .getPurchaseRecord(any()); - } } /** * 非管理端:导出购买记录 */ @Test + @WithMockLoginUser(organId = 1L) void testExportPurchaseRecord_nonManage() throws Exception { - try (MockedStatic mocked = Mockito.mockStatic(SecurityFrameworkUtils.class)) { + PurchaseRecordRespVO respVO = new PurchaseRecordRespVO(); + respVO.setId(1L); + respVO.setOrganId(1L); - mocked.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(false); - mocked.when(SecurityFrameworkUtils::getUserOrganId).thenReturn(1L); + PageResult pageResult = new PageResult<>(); + pageResult.setTotal(1L); + pageResult.setList(List.of(respVO)); - PurchaseRecordRespVO respVO = new PurchaseRecordRespVO(); - respVO.setId(1L); - respVO.setOrganId(1L); + when(purchaseService.getPurchaseRecord(any())) + .thenReturn(pageResult); - PageResult pageResult = new PageResult<>(); - pageResult.setTotal(1L); - pageResult.setList(List.of(respVO)); + mockMvc.perform(get("/system/purchase/export") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); - when(purchaseService.getPurchaseRecord(any())) - .thenReturn(pageResult); - - mockMvc.perform(get("/system/purchase/export") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - - verify(purchaseService, times(1)) - .getPurchaseRecord(any()); - } + verify(purchaseService, times(1)) + .getPurchaseRecord(any()); } -// + /** * 管理端:导出购买记录 */ @Test + @WithMockLoginUser(userType = 2) void testExportPurchaseRecord_manage() throws Exception { - try (MockedStatic mocked = Mockito.mockStatic(SecurityFrameworkUtils.class)) { + PurchaseRecordRespVO respVO = new PurchaseRecordRespVO(); + respVO.setId(1L); - mocked.when(SecurityFrameworkUtils::isManageEndPoint).thenReturn(true); + PageResult pageResult = new PageResult<>(); + pageResult.setTotal(1L); + pageResult.setList(List.of(respVO)); - PurchaseRecordRespVO respVO = new PurchaseRecordRespVO(); - respVO.setId(1L); + when(purchaseService.getPurchaseRecord(any())) + .thenReturn(pageResult); - PageResult pageResult = new PageResult<>(); - pageResult.setTotal(1L); - pageResult.setList(List.of(respVO)); + mockMvc.perform(get("/system/purchase/export") + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); - when(purchaseService.getPurchaseRecord(any())) - .thenReturn(pageResult); - - mockMvc.perform(get("/system/purchase/export") - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()); - - verify(purchaseService, times(1)) - .getPurchaseRecord(any()); - } + verify(purchaseService, times(1)) + .getPurchaseRecord(any()); } } diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/permission/MenuControllerTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/permission/MenuControllerTest.java new file mode 100644 index 000000000..1f101147f --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/controller/admin/permission/MenuControllerTest.java @@ -0,0 +1,340 @@ +package com.cf.imes.module.system.controller.admin.permission; + +import com.cf.imes.framework.common.enums.CommonStatusEnum; +import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants; +import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; +import com.cf.imes.framework.security.test.WithMockLoginUser; +import com.cf.imes.framework.test.core.ut.BaseWebUnitTest; +import com.cf.imes.framework.web.config.ChenfengWebAutoConfiguration; +import com.cf.imes.module.infra.api.logger.ApiErrorLogApi; +import com.cf.imes.module.system.common.WithMockLoginUserExtension; +import com.cf.imes.module.system.controller.admin.permission.vo.menu.*; +import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; +import com.cf.imes.module.system.dal.dataobject.permission.RoleDO; +import com.cf.imes.module.system.dal.dataobject.user.AdminUserDO; +import com.cf.imes.module.system.enums.permission.MenuSourceTypeEnum; +import com.cf.imes.module.system.enums.permission.MenuTypeEnum; +import com.cf.imes.module.system.service.permission.MenuService; +import com.cf.imes.module.system.service.permission.PermissionService; +import com.cf.imes.module.system.service.permission.RoleService; +import com.cf.imes.module.system.service.user.AdminUserService; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.*; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +/** + * 菜单管理单元测试 + * + * @author Gqr + * @since 2026/1/26 16:40 + */ +@WebMvcTest(controllers = MenuController.class, excludeAutoConfiguration = SecurityAutoConfiguration.class) +@ExtendWith(WithMockLoginUserExtension.class) +@Import(ChenfengWebAutoConfiguration.class) +@TestPropertySource(properties = { + "spring.application.name=test-app" +}) +class MenuControllerTest extends BaseWebUnitTest { + + @Autowired + private MockMvc mockMvc; + + @MockitoBean + private MenuService menuService; + + @MockitoBean + private PermissionService permissionService; + + @MockitoBean + private AdminUserService userService; + + @MockitoBean + private RoleService roleService; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private ApiErrorLogApi apiErrorLogApi; + + @Test + void createMenu_success() throws Exception { + MenuSaveVO reqVO = new MenuSaveVO(); + reqVO.setName("新增菜单"); + reqVO.setParentId(0L); + reqVO.setType(MenuTypeEnum.MENU.getType()); + reqVO.setSort(1); + reqVO.setParentId(MenuDO.ID_ROOT); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setSourceType(MenuSourceTypeEnum.PRODUCTION.getType()); + + when(menuService.createMenu(any(MenuSaveVO.class))) + .thenReturn(100L); + + mockMvc.perform(post("/admin-api/system/menu/create") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(100L)); + } + + @Test + void updateMenu_success() throws Exception { + MenuSaveVO reqVO = new MenuSaveVO(); + reqVO.setId(1L); + reqVO.setName("更新菜单"); + reqVO.setType(MenuTypeEnum.MENU.getType()); + reqVO.setSort(1); + reqVO.setParentId(MenuDO.ID_ROOT); + reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); + reqVO.setSourceType(MenuSourceTypeEnum.PRODUCTION.getType()); + + mockMvc.perform(put("/admin-api/system/menu/update") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(true)); + + verify(menuService).updateMenu(any(MenuSaveVO.class)); + } + + @Test + void deleteMenu_success() throws Exception { + mockMvc.perform(delete("/admin-api/system/menu/delete") + .param("id", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(true)); + + verify(menuService).deleteMenu(1L); + } + + @Test + @WithMockLoginUser(userId = 1L, organId = 100L) + void getCustomMenuList_success() throws Exception { + // 准备模拟数据 + List menuList = Arrays.asList(createMenuDO(1L, CommonStatusEnum.ENABLE.getStatus())); + + + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + + Set roleIds = new HashSet<>(); + roleIds.add(1L); + + List roles = Arrays.asList(createRoleDO(1L, CommonStatusEnum.ENABLE.getStatus())); + + Set menuIds = new HashSet<>(); + menuIds.add(1L); + + when(userService.getUser(1L)).thenReturn(user); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + when(roleService.getRoleList1(roleIds)).thenReturn(roles); + when(permissionService.getRoleMenuListByRoleId2(any(Set.class), eq(100L))).thenReturn(menuIds); + when(menuService.getCustomEndPointMenuList(menuIds)).thenReturn(menuList); + + mockMvc.perform(get("/admin-api/system/menu/custom/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isArray()); + + verify(userService).getUser(1L); + verify(permissionService).getUserRoleIdListByUserId(1L); + verify(roleService).getRoleList1(roleIds); + verify(permissionService).getRoleMenuListByRoleId2(any(Set.class), eq(100L)); + verify(menuService).getCustomEndPointMenuList(menuIds); + } + + @Test + @WithMockLoginUser(userId = 1L, organId = 100L, isSuperAdmin = true) + void getMenuList_superAdmin_success() throws Exception { + // 准备模拟数据 + List menuList = Arrays.asList(createMenuDO(1L, CommonStatusEnum.ENABLE.getStatus())); + + when(menuService.getMenuList(any(MenuListReqVO.class))).thenReturn(menuList); + + mockMvc.perform(get("/admin-api/system/menu/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isArray()); + + verify(menuService).getMenuList(any(MenuListReqVO.class)); + } + + @Test + @WithMockLoginUser(userId = 1L, organId = 100L) + void getMenuList_notsuperAdmin_getUserNull() throws Exception { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + when(userService.getUser(loginUserId)).thenReturn(null); + + mockMvc.perform(get("/admin-api/system/menu/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isEmpty()); + } + + @Test + @WithMockLoginUser(userId = 1L, organId = 100L) + void getMenuList_notsuperAdmin_getRolesEmpty() throws Exception { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + when(userService.getUser(loginUserId)).thenReturn(new AdminUserDO()); + + when(permissionService.getUserRoleIdListByUserId(loginUserId)).thenReturn(null); + + mockMvc.perform(get("/admin-api/system/menu/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isEmpty()); + } + + @Test + @WithMockLoginUser(userId = 1L, organId = 100L) + void getMenuList_normalUser_success() throws Exception { + // 准备模拟数据 + List menuList = Arrays.asList(createMenuDO(1L, CommonStatusEnum.ENABLE.getStatus())); + + AdminUserDO user = new AdminUserDO(); + user.setId(1L); + + Set roleIds = new HashSet<>(); + roleIds.add(1L); + + List roles = Arrays.asList(createRoleDO(1L, CommonStatusEnum.ENABLE.getStatus())); + + Set menuIds = new HashSet<>(); + menuIds.add(1L); + + when(userService.getUser(1L)).thenReturn(user); + when(permissionService.getUserRoleIdListByUserId(1L)).thenReturn(roleIds); + when(roleService.getRoleList1(roleIds)).thenReturn(roles); + when(permissionService.getRoleMenuListByRoleId2(any(Set.class), eq(100L))).thenReturn(menuIds); + when(menuService.getMenuList1(menuIds)).thenReturn(menuList); + + mockMvc.perform(get("/admin-api/system/menu/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isArray()); + + verify(userService).getUser(1L); + verify(permissionService).getUserRoleIdListByUserId(1L); + verify(roleService).getRoleList1(roleIds); + verify(permissionService).getRoleMenuListByRoleId2(any(Set.class), eq(100L)); + verify(menuService).getMenuList1(menuIds); + } + + @Test + void getMenuList_noLoginUser_failure() throws Exception { + mockMvc.perform(get("/admin-api/system/menu/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(GlobalErrorCodeConstants.UNAUTHORIZED.getCode())); + } + + @Test + void getCustomMenuList_noLoginUser_failure() throws Exception { + mockMvc.perform(get("/admin-api/system/menu/custom/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(GlobalErrorCodeConstants.UNAUTHORIZED.getCode())); + } + + @Test + @WithMockLoginUser + void getCustomMenuList_getUserNull() throws Exception { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + when(userService.getUser(loginUserId)).thenReturn(null); + + mockMvc.perform(get("/admin-api/system/menu/custom/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isEmpty()); + } + + @Test + @WithMockLoginUser + void getCustomMenuList_getRolesEmpty() throws Exception { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + when(userService.getUser(loginUserId)).thenReturn(new AdminUserDO()); + + when(permissionService.getUserRoleIdListByUserId(loginUserId)).thenReturn(null); + + mockMvc.perform(get("/admin-api/system/menu/custom/list")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data").isEmpty()); + } + + @Test + void getSimpleMenuList_success() throws Exception { + List menuList = Arrays.asList(createMenuDO(1L, CommonStatusEnum.ENABLE.getStatus())); + when(menuService.getMenuListByOrgan(any(MenuListReqVO.class))).thenReturn(menuList); + + mockMvc.perform(get("/admin-api/system/menu/list-all-simple")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").isArray()); + + verify(menuService).getMenuListByOrgan(any(MenuListReqVO.class)); + } + + @Test + void getMenu_success() throws Exception { + MenuDO menuDO = createMenuDO(1L, CommonStatusEnum.ENABLE.getStatus()); + when(menuService.getMenu(1L)).thenReturn(menuDO); + + mockMvc.perform(get("/admin-api/system/menu/get") + .param("id", "1")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.id").value(1L)); + + verify(menuService).getMenu(1L); + } + + @Test + void trans_success() throws Exception { + MenuTransReqVO reqVO = new MenuTransReqVO(); + reqVO.setTargetLanguage("en"); + + mockMvc.perform(post("/admin-api/system/menu/trans") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(reqVO))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data").value(true)); + + verify(menuService).trans(any(MenuTransReqVO.class)); + } + + // 辅助方法:创建MenuDO实例 + private MenuDO createMenuDO(Long id, Integer status) { + MenuDO menuDO = new MenuDO(); + menuDO.setId(id); + menuDO.setStatus(status); + menuDO.setName("测试菜单"); + menuDO.setSort(0); + return menuDO; + } + + // 辅助方法:创建RoleDO实例 + private RoleDO createRoleDO(Long id, Integer status) { + RoleDO roleDO = new RoleDO(); + roleDO.setId(id); + roleDO.setStatus(status); + return roleDO; + } +} \ No newline at end of file diff --git a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/permission/MenuServiceImplTest.java b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/permission/MenuServiceImplTest.java index 94ada713b..28e5ddd3c 100644 --- a/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/permission/MenuServiceImplTest.java +++ b/cf-module-system/cf-module-system-biz/src/test/java/com/cf/imes/module/system/service/permission/MenuServiceImplTest.java @@ -1,74 +1,189 @@ package com.cf.imes.module.system.service.permission; +import cn.hutool.core.util.ObjectUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.cf.imes.framework.common.enums.CommonStatusEnum; -import com.cf.imes.framework.test.core.ut.BaseDbUnitTest; +import com.cf.imes.framework.common.exception.ServiceException; +import com.cf.imes.framework.security.test.WithMockLoginUser; +import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest; +import com.cf.imes.module.system.common.WithMockLoginUserExtension; import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO; +import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuTransReqVO; import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; +import com.cf.imes.module.system.dal.dataobject.permission.MenuI18nDO; +import com.cf.imes.module.system.dal.mysql.permission.MenuI18nMapper; import com.cf.imes.module.system.dal.mysql.permission.MenuMapper; +import com.cf.imes.module.system.enums.common.LanguageEnum; import com.cf.imes.module.system.enums.permission.MenuSourceTypeEnum; import com.cf.imes.module.system.enums.permission.MenuTypeEnum; +import com.cf.imes.module.system.framework.trans.config.TransApi; +import com.cf.imes.module.system.framework.trans.config.TransProperties; import com.cf.imes.module.system.service.organ.OrganService; import org.junit.jupiter.api.Test; -import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.context.annotation.Import; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import jakarta.annotation.Resource; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import static com.cf.imes.framework.common.util.collection.SetUtils.asSet; -import static com.cf.imes.framework.common.util.object.ObjectUtils.cloneIgnoreId; import static com.cf.imes.framework.test.core.util.AssertUtils.assertPojoEquals; import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException; import static com.cf.imes.framework.test.core.util.RandomUtils.*; import static com.cf.imes.module.system.enums.ErrorCodeConstants.*; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; -@Import(MenuServiceImpl.class) -public class MenuServiceImplTest extends BaseDbUnitTest { +@ExtendWith(WithMockLoginUserExtension.class) +class MenuServiceImplTest extends BaseMockitoUnitTest { - @Resource + @InjectMocks private MenuServiceImpl menuService; - @Resource + @Mock private MenuMapper menuMapper; - @MockBean + @Mock private PermissionService permissionService; - @MockBean - private OrganService tenantService; + + @Mock + private OrganService organService; + + @Mock + private RedisTemplate redisTemplate; + + @Mock + private Cursor cursor; + + @Mock + private ThreadPoolTaskExecutor threadPoolTaskExecutor; + + @Mock + private MenuI18nMapper menuI18nMapper; + + @Mock + private TransProperties transProperties; + @Test - public void testCreateMenu_success() { + @WithMockLoginUser(organId = 1L) + void testCreateMenu_success() { // mock 数据(构造父菜单) MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", 0L); - menuMapper.insert(menuDO); Long parentId = menuDO.getId(); + when(menuMapper.selectById(parentId)).thenReturn(menuDO); + + // 校验自己 + when(menuMapper.selectByParentIdAndNameAndSourceType(any(), any(), any())).thenReturn(null); + + // 模拟 Mapper 插入时生成 ID + doAnswer(invocation -> { + MenuDO mdo = invocation.getArgument(0); + mdo.setId(100L); // 模拟自增主键 + return null; + }).when(menuMapper).insert(any(MenuDO.class)); + // 准备参数 MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> { o.setParentId(parentId); - o.setName("testSonName"); - o.setType(MenuTypeEnum.MENU.getType()); + o.setType(MenuTypeEnum.BUTTON.getType()); }).setId(null); // 防止 id 被赋值 + + // redis mock + when(redisTemplate.scan(any())).thenReturn(cursor); + when(cursor.hasNext()).thenReturn(true, false); + + // mock executor + doAnswer((InvocationOnMock invocation) -> { + ((Runnable) invocation.getArguments()[0]).run(); + return null; + }).when(threadPoolTaskExecutor).execute(any(Runnable.class)); + Long menuId = menuService.createMenu(reqVO); - // 校验记录的属性是否正确 - MenuDO dbMenu = menuMapper.selectById(menuId); - assertPojoEquals(reqVO, dbMenu, "id"); + // 断言 + assertEquals(100L, menuId); + verify(redisTemplate, times(1)).convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, ""); + verify(redisTemplate, times(2)).scan(any()); + verify(menuMapper).insert(argThat((MenuDO menuArg) -> + ObjectUtil.equals(menuArg.getName(), reqVO.getName()) + && ObjectUtil.equals(menuArg.getPermission(), reqVO.getPermission()) + && ObjectUtil.equals(menuArg.getType(), reqVO.getType()) + && ObjectUtil.equals(menuArg.getSort(), reqVO.getSort()) + && ObjectUtil.equals(menuArg.getParentId(), reqVO.getParentId()) + && ObjectUtil.equals("", menuArg.getPath()) + && ObjectUtil.equals("", menuArg.getIcon()) + && ObjectUtil.equals("", menuArg.getComponent()) + && ObjectUtil.equals("", menuArg.getComponentName()) + && ObjectUtil.equals(menuArg.getStatus(), reqVO.getStatus()) + && ObjectUtil.equals(menuArg.getVisible(), reqVO.getVisible()) + && ObjectUtil.equals(menuArg.getKeepAlive(), reqVO.getKeepAlive()) + && ObjectUtil.equals(menuArg.getAlwaysShow(), reqVO.getAlwaysShow()) + && ObjectUtil.equals(menuArg.getDescription(), reqVO.getDescription()) + && ObjectUtil.equals(menuArg.getSourceType(), reqVO.getSourceType()) + )); } @Test - public void testUpdateMenu_success() { + @WithMockLoginUser + void testCreateMenu_fail_sourceTypePermissionError() { + // mock 数据(构造父菜单) + MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, + "parent", 0L); + Long parentId = menuDO.getId(); + when(menuMapper.selectById(parentId)).thenReturn(menuDO); + + // 校验自己 + when(menuMapper.selectByParentIdAndNameAndSourceType(any(), any(), any())).thenReturn(null); + + // 准备参数 + MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> { + o.setParentId(parentId); + o.setSourceType(MenuSourceTypeEnum.MANAGEMENT.getType()); + }).setId(null); // 防止 id 被赋值 + + assertServiceException(() -> menuService.createMenu(reqVO), MANAGEMENT_MENU_OPERATION_PERMISSION_ERROR); + } + + @Test + @WithMockLoginUser + void testUpdateMenu_success() { + // 校验父级 + MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, + "parent", 0L); + // mock 数据(构造父子菜单) - MenuDO sonMenuDO = createParentAndSonMenu(); + MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName", + menuDO.getParentId()); + when(menuMapper.selectById(any())).thenReturn(sonMenuDO).thenReturn(menuDO); + when(menuMapper.selectByParentIdAndNameAndSourceType(any(), any(), any())).thenReturn(sonMenuDO); + Long sonId = sonMenuDO.getId(); // 准备参数 MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> { @@ -76,17 +191,67 @@ public class MenuServiceImplTest extends BaseDbUnitTest { o.setName("testSonName"); // 修改名字 o.setParentId(sonMenuDO.getParentId()); o.setType(MenuTypeEnum.MENU.getType()); + o.setPermission(null); + }); + + // redis mock + when(redisTemplate.scan(any())).thenReturn(cursor); + when(cursor.hasNext()).thenReturn(true, false); + + // mock executor + doAnswer((InvocationOnMock invocation) -> { + ((Runnable) invocation.getArguments()[0]).run(); + return null; + }).when(threadPoolTaskExecutor).execute(any(Runnable.class)); + + + // 调用 + menuService.updateMenu(reqVO); + + // 校验记录的属性是否正确 + verify(menuMapper, times(1)).updateById(any(MenuDO.class)); + verify(redisTemplate, times(2)).scan(any()); + verify(redisTemplate, times(1)).convertAndSend(com.cf.imes.framework.redis.constants.RedisKeyConstants.PERMISSION_REFRESH, ""); + } + + @Test + @WithMockLoginUser + void testUpdateMenu_success_flushCache() { + // 校验父级 + MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, + "parent", 0L); + + // mock 数据(构造父子菜单) + MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName", + menuDO.getParentId()); + // same permission + sonMenuDO.setPermission("permission"); + + when(menuMapper.selectById(any())).thenReturn(sonMenuDO).thenReturn(menuDO); + when(menuMapper.selectByParentIdAndNameAndSourceType(any(), any(), any())).thenReturn(sonMenuDO); + + Long sonId = sonMenuDO.getId(); + // 准备参数 + MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> { + o.setId(sonId); + o.setName("testSonName"); // 修改名字 + o.setParentId(sonMenuDO.getParentId()); + o.setType(MenuTypeEnum.MENU.getType()); + // same permission + o.setPermission("permission"); }); // 调用 menuService.updateMenu(reqVO); + // 校验记录的属性是否正确 - MenuDO dbMenu = menuMapper.selectById(sonId); - assertPojoEquals(reqVO, dbMenu); + verify(menuMapper, times(1)).updateById(any(MenuDO.class)); + verify(redisTemplate, never()).scan(any()); + verify(redisTemplate, never()).convertAndSend(anyString(), anyString()); } @Test - public void testUpdateMenu_sonIdNotExist() { + void testUpdateMenu_sonIdNotExist() { // 准备参数 MenuSaveVO reqVO = randomPojo(MenuSaveVO.class); // 调用,并断言异常 @@ -94,46 +259,44 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testDeleteMenu_success() { + @WithMockLoginUser + void testDeleteMenu_success() { // mock 数据 MenuDO menuDO = randomPojo(MenuDO.class); - menuMapper.insert(menuDO); // 准备参数 Long id = menuDO.getId(); + when(menuMapper.selectCountByParentId(id)).thenReturn(0L); + when(menuMapper.selectById(id)).thenReturn(menuDO); // 调用 menuService.deleteMenu(id); // 断言 - MenuDO dbMenuDO = menuMapper.selectById(id); - assertNull(dbMenuDO); - verify(permissionService).processMenuDeleted(id); + verify(menuMapper, times(1)).deleteById(id); + verify(permissionService, times(1)).processMenuDeleted(id); } @Test - public void testDeleteMenu_menuNotExist() { + void testDeleteMenu_menuNotExist() { assertServiceException(() -> menuService.deleteMenu(randomLongId()), MENU_NOT_EXISTS); } @Test - public void testDeleteMenu_existChildren() { - // mock 数据(构造父子菜单) - MenuDO sonMenu = createParentAndSonMenu(); - // 准备参数 - Long parentId = sonMenu.getParentId(); + void testDeleteMenu_existChildren() { + Long menuId = 100L; + when(menuMapper.selectCountByParentId(menuId)).thenReturn(1L); // 调用并断言异常 - assertServiceException(() -> menuService.deleteMenu(parentId), MENU_EXISTS_CHILDREN); + assertServiceException(() -> menuService.deleteMenu(menuId), MENU_EXISTS_CHILDREN); } @Test - public void testGetMenuList_all() { + void testGetMenuList_all() { // mock 数据 MenuDO menu100 = randomPojo(MenuDO.class); - menuMapper.insert(menu100); MenuDO menu101 = randomPojo(MenuDO.class); - menuMapper.insert(menu101); - // 准备参数 + + when(menuMapper.selectList()).thenReturn(List.of(menu100, menu101)); // 调用 List list = menuService.getMenuList(); @@ -144,16 +307,13 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testGetMenuList() { + void testGetMenuList() { // mock 数据 MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setName("晨丰").setStatus(CommonStatusEnum.ENABLE.getStatus())); - menuMapper.insert(menuDO); - // 测试 status 不匹配 - menuMapper.insert(cloneIgnoreId(menuDO, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()))); - // 测试 name 不匹配 - menuMapper.insert(cloneIgnoreId(menuDO, o -> o.setName("艿"))); + // 准备参数 MenuListReqVO reqVO = new MenuListReqVO().setName("芋").setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(menuMapper.selectList(reqVO)).thenReturn(List.of(menuDO)); // 调用 List result = menuService.getMenuList(reqVO); @@ -163,22 +323,21 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testGetMenuListByTenant() { + void testGetMenuListByTenant() { // mock 数据 MenuDO menu100 = randomPojo(MenuDO.class, o -> o.setId(100L).setStatus(CommonStatusEnum.ENABLE.getStatus())); - menuMapper.insert(menu100); MenuDO menu101 = randomPojo(MenuDO.class, o -> o.setId(101L).setStatus(CommonStatusEnum.DISABLE.getStatus())); - menuMapper.insert(menu101); MenuDO menu102 = randomPojo(MenuDO.class, o -> o.setId(102L).setStatus(CommonStatusEnum.ENABLE.getStatus())); - menuMapper.insert(menu102); + // mock 过滤菜单 - Set menuIds = asSet(100L, 101L); - doNothing().when(tenantService).handleOrganMenu(argThat(handler -> { + Set menuIds = asSet(100L); + doNothing().when(organService).handleOrganMenu(argThat(handler -> { handler.handle(menuIds); return true; })); // 准备参数 MenuListReqVO reqVO = new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()); + when(menuMapper.selectList(reqVO)).thenReturn(new ArrayList<>(Arrays.asList(menu100, menu101, menu102))); // 调用 List result = menuService.getMenuListByOrgan(reqVO); @@ -188,15 +347,32 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testGetMenuIdListByPermissionFromCache() { + void testGetMenuList1_empty() { + List menuList1 = menuService.getMenuList1(Collections.emptyList()); + + assertEquals(0, menuList1.size()); + } + + @Test + void testGetMenuList1_success() { + MenuDO menuDO = randomPojo(MenuDO.class); + Set ids = Collections.singleton(menuDO.getId()); + when(menuMapper.selectByIds(ids)).thenReturn(List.of(menuDO)); + + List menuList1 = menuService.getMenuList1(ids); + + assertEquals(menuDO, menuList1.get(0)); + } + + @Test + void testGetMenuIdListByPermissionFromCache() { // mock 数据 MenuDO menu100 = randomPojo(MenuDO.class); - menuMapper.insert(menu100); - MenuDO menu101 = randomPojo(MenuDO.class); - menuMapper.insert(menu101); // 准备参数 String permission = menu100.getPermission(); + when(menuMapper.selectListByPermission(menu100.getPermission())).thenReturn(List.of(menu100)); + // 调用 List ids = menuService.getMenuIdListByPermissionFromCache(permission); // 断言 @@ -205,14 +381,13 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testGetMenuList_ids() { + void testGetMenuList_ids() { // mock 数据 MenuDO menu100 = randomPojo(MenuDO.class); - menuMapper.insert(menu100); - MenuDO menu101 = randomPojo(MenuDO.class); - menuMapper.insert(menu101); + // 准备参数 Collection ids = Collections.singleton(menu100.getId()); + when(menuMapper.selectByIds(ids)).thenReturn(List.of(menu100)); // 调用 List list = menuService.getMenuList(ids); @@ -222,12 +397,13 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testGetMenu() { + void testGetMenu() { // mock 数据 MenuDO menu = randomPojo(MenuDO.class); - menuMapper.insert(menu); + // 准备参数 Long id = menu.getId(); + when(menuMapper.selectById(id)).thenReturn(menu); // 调用 MenuDO dbMenu = menuService.getMenu(id); @@ -236,38 +412,41 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testValidateParentMenu_success() { + void testValidateParentMenu_success() { // mock 数据 MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", 0L); - menuMapper.insert(menuDO); + // 准备参数 Long parentId = menuDO.getId(); + when(menuMapper.selectById(parentId)).thenReturn(menuDO); // 调用,无需断言 menuService.validateParentMenu(parentId, null); + menuService.validateParentMenu(null, null); } @Test - public void testValidateParentMenu_canNotSetSelfToBeParent() { + void testValidateParentMenu_canNotSetSelfToBeParent() { // 调用,并断言异常 assertServiceException(() -> menuService.validateParentMenu(1L, 1L), MENU_PARENT_ERROR); } @Test - public void testValidateParentMenu_parentNotExist() { + void testValidateParentMenu_parentNotExist() { // 调用,并断言异常 assertServiceException(() -> menuService.validateParentMenu(randomLongId(), null), MENU_PARENT_NOT_EXISTS); } @Test - public void testValidateParentMenu_parentTypeError() { + void testValidateParentMenu_parentTypeError() { // mock 数据 MenuDO menuDO = buildMenuDO(MenuTypeEnum.BUTTON, "parent", 0L); - menuMapper.insert(menuDO); + // 准备参数 Long parentId = menuDO.getId(); + when(menuMapper.selectById(parentId)).thenReturn(menuDO); // 调用,并断言异常 assertServiceException(() -> menuService.validateParentMenu(parentId, null), @@ -275,9 +454,9 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testValidateMenu_success() { + void testValidateMenu_success() { // mock 父子菜单 - MenuDO sonMenu = createParentAndSonMenu(); + MenuDO sonMenu = randomPojo(MenuDO.class); // 准备参数 Long parentId = sonMenu.getParentId(); Long otherSonMenuId = randomLongId(); @@ -288,44 +467,221 @@ public class MenuServiceImplTest extends BaseDbUnitTest { } @Test - public void testValidateMenu_sonMenuNameDuplicate() { + void testValidateMenu_sonMenuNameDuplicate() { // mock 父子菜单 - MenuDO sonMenu = createParentAndSonMenu(); + MenuDO sonMenu = randomPojo(MenuDO.class); // 准备参数 Long parentId = sonMenu.getParentId(); Long otherSonMenuId = randomLongId(); String otherSonMenuName = sonMenu.getName(); //相同名称 + when(menuMapper.selectByParentIdAndNameAndSourceType(parentId, otherSonMenuName, MenuSourceTypeEnum.PRODUCTION.getType())).thenReturn(sonMenu); + // 调用,并断言异常 assertServiceException(() -> menuService.validateMenu(parentId, otherSonMenuName, otherSonMenuId, MenuSourceTypeEnum.PRODUCTION.getType()), MENU_NAME_DUPLICATE); } - // ====================== 初始化方法 ====================== + @Test + void testValidateMenu_sonMenuNameDuplicate_whenNull() { + // mock 父子菜单 + MenuDO sonMenu = randomPojo(MenuDO.class); + // 准备参数 + Long parentId = sonMenu.getParentId(); + String otherSonMenuName = sonMenu.getName(); //相同名称 - /** - * 插入父子菜单,返回子菜单 - * - * @return 子菜单 - */ - private MenuDO createParentAndSonMenu() { - // 构造父子菜单 - MenuDO parentMenuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", MenuDO.ID_ROOT); - menuMapper.insert(parentMenuDO); - // 构建子菜单 - MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName", - parentMenuDO.getParentId()); - menuMapper.insert(sonMenuDO); - return sonMenuDO; + when(menuMapper.selectByParentIdAndNameAndSourceType(parentId, otherSonMenuName, MenuSourceTypeEnum.PRODUCTION.getType())).thenReturn(sonMenu); + + // 调用,并断言异常 + assertServiceException(() -> menuService.validateMenu(parentId, otherSonMenuName, null, MenuSourceTypeEnum.PRODUCTION.getType()), + MENU_NAME_DUPLICATE); } + + @Test + void testGetManageEndPointMenuList() { + List mockList = List.of(new MenuDO()); + + when(menuMapper.selectListIn( + anyCollection(), + eq(true), + anyString() + )).thenReturn(mockList); + + List result = menuService.getManageEndPointMenuList(List.of(1L, 2L)); + + assertSame(mockList, result); + + verify(menuMapper).selectListIn( + anyCollection(), + eq(true), + anyString() + ); + } + + @Test + void testGetCustomEndPointMenuList() { + List mockList = List.of(new MenuDO()); + + when(menuMapper.selectListIn( + anyCollection(), + eq(false), + anyString() + )).thenReturn(mockList); + + List result = menuService.getCustomEndPointMenuList(List.of(1L, 2L)); + + assertSame(mockList, result); + + verify(menuMapper).selectListIn( + anyCollection(), + eq(false), + anyString() + ); + } + + // ====================== 初始化方法 ====================== + private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId) { return buildMenuDO(type, name, parentId, randomCommonStatus()); } private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) { - return randomPojo(MenuDO.class, o -> o.setId(null).setName(name).setParentId(parentId) + return randomPojo(MenuDO.class, o -> o.setName(name).setParentId(parentId) .setType(type.getType()).setStatus(status)); } + @Test + void testTrans_errorCode() { + // mock properties + when(transProperties.getAppId()).thenReturn("appId"); + when(transProperties.getSecurityKey()).thenReturn("key"); + when(transProperties.getApiHost()).thenReturn("host"); + + MenuTransReqVO req = new MenuTransReqVO(); + req.setTargetLanguage("en"); + + when(menuMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(randomPojo(MenuDO.class))); + + String errorJson = """ + { + "error_code": "54001" + } + """; + + try (MockedConstruction mocked = + Mockito.mockConstruction(TransApi.class, + (mock, context) -> when(mock.getTransResult(any(), any(), any())) + .thenReturn(errorJson))) { + + assertThrows(ServiceException.class, + () -> menuService.trans(req)); + } + + verify(menuI18nMapper, never()).insertBatch(any()); + } + + + @Test + void testTrans_success_en() { + // mock properties + when(transProperties.getAppId()).thenReturn("appId"); + when(transProperties.getSecurityKey()).thenReturn("key"); + when(transProperties.getApiHost()).thenReturn("host"); + + MenuTransReqVO req = new MenuTransReqVO(); + req.setTargetLanguage("en"); + + when(menuMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(randomPojo(MenuDO.class, o -> { + o.setName("系统管理"); + }))); + + String okJson = """ + { + "trans_result": [ + { "src": "系统管理", "dst": "system management" } + ] + } + """; + + try (MockedConstruction mocked = + Mockito.mockConstruction(TransApi.class, + (mock, context) -> when(mock.getTransResult(any(), any(), any())) + .thenReturn(okJson))) { + + menuService.trans(req); + } + + ArgumentCaptor> captor = + ArgumentCaptor.forClass(List.class); + + verify(menuI18nMapper).insertBatch(captor.capture()); + + MenuI18nDO i18n = captor.getValue().get(0); + assertEquals("System Management", i18n.getMenuName()); + assertEquals("en", i18n.getLocale()); + } + + @Test + void testTrans_srcNotMatch() { + MenuTransReqVO req = new MenuTransReqVO(); + req.setTargetLanguage("en"); + + when(menuMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(randomPojo(MenuDO.class, o -> { + o.setName("系统管理"); + }))); + + String json = """ + { + "trans_result": [ + { "src": "用户管理", "dst": "user management" } + ] + } + """; + + try (MockedConstruction mocked = + Mockito.mockConstruction(TransApi.class, + (mock, context) -> when(mock.getTransResult(any(), any(), any())) + .thenReturn(json))) { + + menuService.trans(req); + } + + verify(menuI18nMapper, never()).insertBatch(any()); + } + + @Test + void testTrans_partialSuccess() { + MenuTransReqVO req = new MenuTransReqVO(); + req.setTargetLanguage(LanguageEnum.ZH.getLanguage()); + + when(menuMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn( + List.of( + randomPojo(MenuDO.class, o -> o.setName("系统管理")), + randomPojo(MenuDO.class, o -> o.setName("用户管理")) + ) + ); + + String json = """ + { + "trans_result": [ + { "src": "系统管理", "dst": "system management" }, + { "src": "角色管理", "dst": "role management" } + ] + } + """; + + try (MockedConstruction mocked = + Mockito.mockConstruction(TransApi.class, + (mock, context) -> when(mock.getTransResult(any(), any(), any())) + .thenReturn(json))) { + menuService.trans(req); + } + + ArgumentCaptor> captor = + ArgumentCaptor.forClass(List.class); + + verify(menuI18nMapper).insertBatch(captor.capture()); + assertEquals(1, captor.getValue().size()); + } }