Bladeren bron

!13 user模块单元测试
Merge pull request !13 from 化身孤岛的鲸/feat_test-user

芋道源码 4 jaren geleden
bovenliggende
commit
4a322ed72a

+ 10 - 5
pom.xml

@@ -46,7 +46,12 @@
         <easyexcel.verion>2.2.7</easyexcel.verion>
         <velocity.version>2.2</velocity.version>
         <screw.version>1.0.5</screw.version>
+        <podam.version>7.2.6.RELEASE</podam.version>
+        <jedis-mock.version>0.1.16</jedis-mock.version>
         <!-- 三方云服务相关 -->
+        <aliyun-java-sdk-core.version>4.5.18</aliyun-java-sdk-core.version>
+        <aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
+        <yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
     </properties>
 
     <!-- 依赖声明 -->
@@ -213,14 +218,14 @@
         <dependency>
             <groupId>com.github.fppt</groupId> <!-- 单元测试,我们采用内嵌的 Redis 数据库 -->
             <artifactId>jedis-mock</artifactId>
-            <version>0.1.16</version>
+            <version>${jedis-mock.version}</version>
             <scope>test</scope>
         </dependency>
 
         <dependency>
             <groupId>uk.co.jemos.podam</groupId> <!-- 单元测试,随机生成 POJO 类 -->
             <artifactId>podam</artifactId>
-            <version>7.2.6.RELEASE</version>
+            <version>${podam.version}</version>
             <scope>test</scope>
         </dependency>
 
@@ -278,18 +283,18 @@
         <dependency>
             <groupId>com.yunpian.sdk</groupId>
             <artifactId>yunpian-java-sdk</artifactId>
-            <version>1.2.7</version>
+            <version>${yunpian-java-sdk.version}</version>
         </dependency>
 
         <dependency>
             <groupId>com.aliyun</groupId>
             <artifactId>aliyun-java-sdk-core</artifactId>
-            <version>4.5.18</version>
+            <version>${aliyun-java-sdk-core.version}</version>
         </dependency>
         <dependency>
             <groupId>com.aliyun</groupId>
             <artifactId>aliyun-java-sdk-dysmsapi</artifactId>
-            <version>2.1.0</version>
+            <version>${aliyun-java-sdk-dysmsapi.version}</version>
         </dependency>
         <!-- SMS SDK end -->
 

+ 1 - 1
ruoyi-ui/src/views/system/user/index.vue

@@ -362,7 +362,7 @@ export default {
   created() {
     this.getList();
     this.getTreeselect();
-    this.getConfigKey("sys.user.initPassword").then(response => {
+    this.getConfigKey("sys.user.init-password").then(response => {
       this.initPassword = response.msg;
     });
   },

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java

@@ -149,9 +149,9 @@ public class SysUserController {
         // 手动创建导出 demo
         List<SysUserImportExcelVO> list = Arrays.asList(
                 SysUserImportExcelVO.builder().username("yudao").deptId(1L).email("yudao@iocoder.cn").mobile("15601691300")
-                        .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSEX()).build(),
+                        .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSex()).build(),
                 SysUserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300")
-                        .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSEX()).build()
+                        .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSex()).build()
         );
 
         // 输出

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/profile/SysUserProfileUpdateReqVO.java

@@ -3,9 +3,9 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.profile;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.Email;
-import javax.validation.constraints.Pattern;
 import javax.validation.constraints.Size;
 
 @ApiModel("用户个人信息更新 Request VO")
@@ -22,7 +22,7 @@ public class SysUserProfileUpdateReqVO {
     private String email;
 
     @ApiModelProperty(value = "手机号码", example = "15601691300")
-    @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
+    @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
     private String mobile;
 
     @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SysSexEnum 枚举类")

+ 2 - 2
src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserBaseVO.java

@@ -2,10 +2,10 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.user;
 
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import org.hibernate.validator.constraints.Length;
 
 import javax.validation.constraints.Email;
 import javax.validation.constraints.NotBlank;
-import javax.validation.constraints.Pattern;
 import javax.validation.constraints.Size;
 import java.util.Set;
 
@@ -40,7 +40,7 @@ public class SysUserBaseVO {
     private String email;
 
     @ApiModelProperty(value = "手机号码", example = "15601691300")
-    @Pattern(regexp = "^1(3|4|5|7|8)\\d{9}$",message = "手机号码格式错误")
+    @Length(min = 11, max = 11, message = "手机号长度必须 11 位")
     private String mobile;
 
     @ApiModelProperty(value = "用户性别", example = "1", notes = "参见 SysSexEnum 枚举类")

+ 9 - 1
src/main/java/cn/iocoder/dashboard/modules/system/enums/common/SysSexEnum.java

@@ -3,6 +3,11 @@ package cn.iocoder.dashboard.modules.system.enums.common;
 import lombok.AllArgsConstructor;
 import lombok.Getter;
 
+/**
+ * 性别的枚举值
+ *
+ * @author 芋道源码
+ */
 @Getter
 @AllArgsConstructor
 public enum SysSexEnum {
@@ -10,6 +15,9 @@ public enum SysSexEnum {
     MALE(1), // 男
     FEMALE(2); // 女
 
-    private final Integer SEX;
+    /**
+     * 性别
+     */
+    private final Integer sex;
 
 }

+ 41 - 40
src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java

@@ -6,17 +6,11 @@ import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.common.exception.ServiceException;
-import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.modules.infra.service.file.InfFileService;
 import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
 import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
-import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserCreateReqVO;
-import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO;
-import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserImportExcelVO;
-import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserImportRespVO;
-import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageReqVO;
-import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO;
+import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*;
 import cn.iocoder.dashboard.modules.system.convert.user.SysUserConvert;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO;
@@ -26,21 +20,18 @@ import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
 import cn.iocoder.dashboard.modules.system.service.dept.SysPostService;
 import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
 import cn.iocoder.dashboard.util.collection.CollectionUtils;
+import com.google.common.annotations.VisibleForTesting;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
 import org.springframework.security.crypto.password.PasswordEncoder;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
+import static cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
 
 
@@ -53,6 +44,9 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
 @Slf4j
 public class SysUserServiceImpl implements SysUserService {
 
+    @Value("${sys.user.init-password:yudaoyuanma}")
+    private String userInitPassword;
+
     @Resource
     private SysUserMapper userMapper;
 
@@ -142,8 +136,6 @@ public class SysUserServiceImpl implements SysUserService {
         updateObj.setId(id);
         updateObj.setStatus(status);
         userMapper.updateById(updateObj);
-        // 删除用户关联数据
-        permissionService.processUserDeleted(id);
     }
 
     @Override
@@ -152,6 +144,8 @@ public class SysUserServiceImpl implements SysUserService {
         this.checkUserExists(id);
         // 删除用户
         userMapper.deleteById(id);
+        // 删除用户关联数据
+        permissionService.processUserDeleted(id);
     }
 
     @Override
@@ -221,17 +215,19 @@ public class SysUserServiceImpl implements SysUserService {
         this.checkPostEnable(postIds);
     }
 
-    private void checkUserExists(Long id) {
+    @VisibleForTesting
+    void checkUserExists(Long id) {
         if (id == null) {
             return;
         }
         SysUserDO user = userMapper.selectById(id);
         if (user == null) {
-            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
+            throw exception(USER_NOT_EXISTS);
         }
     }
 
-    private void checkUsernameUnique(Long id, String username) {
+    @VisibleForTesting
+    void checkUsernameUnique(Long id, String username) {
         if (StrUtil.isBlank(username)) {
             return;
         }
@@ -241,14 +237,15 @@ public class SysUserServiceImpl implements SysUserService {
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的用户
         if (id == null) {
-            throw ServiceExceptionUtil.exception(USER_USERNAME_EXISTS);
+            throw exception(USER_USERNAME_EXISTS);
         }
         if (!user.getId().equals(id)) {
-            throw ServiceExceptionUtil.exception(USER_USERNAME_EXISTS);
+            throw exception(USER_USERNAME_EXISTS);
         }
     }
 
-    private void checkEmailUnique(Long id, String email) {
+    @VisibleForTesting
+    void checkEmailUnique(Long id, String email) {
         if (StrUtil.isBlank(email)) {
             return;
         }
@@ -258,14 +255,15 @@ public class SysUserServiceImpl implements SysUserService {
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的用户
         if (id == null) {
-            throw ServiceExceptionUtil.exception(USER_EMAIL_EXISTS);
+            throw exception(USER_EMAIL_EXISTS);
         }
         if (!user.getId().equals(id)) {
-            throw ServiceExceptionUtil.exception(USER_EMAIL_EXISTS);
+            throw exception(USER_EMAIL_EXISTS);
         }
     }
 
-    private void checkMobileUnique(Long id, String mobile) {
+    @VisibleForTesting
+    void checkMobileUnique(Long id, String mobile) {
         if (StrUtil.isBlank(mobile)) {
             return;
         }
@@ -275,42 +273,44 @@ public class SysUserServiceImpl implements SysUserService {
         }
         // 如果 id 为空,说明不用比较是否为相同 id 的用户
         if (id == null) {
-            throw ServiceExceptionUtil.exception(USER_MOBILE_EXISTS);
+            throw exception(USER_MOBILE_EXISTS);
         }
         if (!user.getId().equals(id)) {
-            throw ServiceExceptionUtil.exception(USER_MOBILE_EXISTS);
+            throw exception(USER_MOBILE_EXISTS);
         }
     }
 
-    private void checkDeptEnable(Long deptId) {
+    @VisibleForTesting
+    void checkDeptEnable(Long deptId) {
         if (deptId == null) { // 允许不选择
             return;
         }
         SysDeptDO dept = deptService.getDept(deptId);
         if (dept == null) {
-            throw ServiceExceptionUtil.exception(DEPT_NOT_FOUND);
+            throw exception(DEPT_NOT_FOUND);
         }
         if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
-            throw ServiceExceptionUtil.exception(DEPT_NOT_ENABLE);
+            throw exception(DEPT_NOT_ENABLE);
         }
     }
 
-    private void checkPostEnable(Set<Long> postIds) {
+    @VisibleForTesting
+    void checkPostEnable(Set<Long> postIds) {
         if (CollUtil.isEmpty(postIds)) { // 允许不选择
             return;
         }
         List<SysPostDO> posts = postService.getPosts(postIds, null);
         if (CollUtil.isEmpty(posts)) {
-            throw ServiceExceptionUtil.exception(POST_NOT_FOUND);
+            throw exception(POST_NOT_FOUND);
         }
         Map<Long, SysPostDO> postMap = CollectionUtils.convertMap(posts, SysPostDO::getId);
         postIds.forEach(postId -> {
             SysPostDO post = postMap.get(postId);
             if (post == null) {
-                throw ServiceExceptionUtil.exception(POST_NOT_FOUND);
+                throw exception(POST_NOT_FOUND);
             }
             if (!CommonStatusEnum.ENABLE.getStatus().equals(post.getStatus())) {
-                throw ServiceExceptionUtil.exception(POST_NOT_ENABLE, post.getName());
+                throw exception(POST_NOT_ENABLE, post.getName());
             }
         });
     }
@@ -321,13 +321,14 @@ public class SysUserServiceImpl implements SysUserService {
      * @param id          用户 id
      * @param oldPassword 旧密码
      */
-    private void checkOldPassword(Long id, String oldPassword) {
+    @VisibleForTesting
+    void checkOldPassword(Long id, String oldPassword) {
         SysUserDO user = userMapper.selectById(id);
         if (user == null) {
-            throw ServiceExceptionUtil.exception(USER_NOT_EXISTS);
+            throw exception(USER_NOT_EXISTS);
         }
         if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
-            throw ServiceExceptionUtil.exception(USER_PASSWORD_FAILED);
+            throw exception(USER_PASSWORD_FAILED);
         }
     }
 
@@ -335,7 +336,7 @@ public class SysUserServiceImpl implements SysUserService {
     @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
     public SysUserImportRespVO importUsers(List<SysUserImportExcelVO> importUsers, boolean isUpdateSupport) {
         if (CollUtil.isEmpty(importUsers)) {
-            throw ServiceExceptionUtil.exception(USER_IMPORT_LIST_IS_EMPTY);
+            throw exception(USER_IMPORT_LIST_IS_EMPTY);
         }
         SysUserImportRespVO respVO = SysUserImportRespVO.builder().createUsernames(new ArrayList<>())
             .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
@@ -351,8 +352,8 @@ public class SysUserServiceImpl implements SysUserService {
             // 判断如果不存在,在进行插入
             SysUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
             if (existUser == null) {
-                // TODO 芋艿:初始密码
-                userMapper.insert(SysUserConvert.INSTANCE.convert(importUser));
+                userMapper.insert(SysUserConvert.INSTANCE.convert(importUser)
+                    .setPassword(passwordEncoder.encode(userInitPassword))); // 设置默认密码
                 respVO.getCreateUsernames().add(importUser.getUsername());
                 return;
             }

+ 2 - 9
src/test/java/cn/iocoder/dashboard/modules/system/service/auth/SysUserSessionServiceImplTest.java

@@ -1,7 +1,6 @@
 package cn.iocoder.dashboard.modules.system.service.auth;
 
 import static cn.hutool.core.util.RandomUtil.randomEle;
-import static cn.iocoder.dashboard.modules.system.dal.redis.SysRedisKeyConstants.LOGIN_USER;
 import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.dashboard.util.RandomUtils.randomDate;
 import static cn.iocoder.dashboard.util.RandomUtils.randomLongId;
@@ -25,7 +24,6 @@ import javax.annotation.Resource;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
-import org.springframework.data.redis.core.StringRedisTemplate;
 
 import cn.hutool.core.date.DateUtil;
 import cn.iocoder.dashboard.BaseDbAndRedisUnitTest;
@@ -33,10 +31,6 @@ import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
 import cn.iocoder.dashboard.common.pojo.PageResult;
 import cn.iocoder.dashboard.framework.security.config.SecurityProperties;
 import cn.iocoder.dashboard.framework.security.core.LoginUser;
-import cn.iocoder.dashboard.modules.infra.controller.job.vo.job.InfJobPageReqVO;
-import cn.iocoder.dashboard.modules.infra.dal.dataobject.job.InfJobDO;
-import cn.iocoder.dashboard.modules.infra.enums.config.InfConfigTypeEnum;
-import cn.iocoder.dashboard.modules.infra.enums.job.InfJobStatusEnum;
 import cn.iocoder.dashboard.modules.system.controller.auth.vo.session.SysUserSessionPageReqVO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.auth.SysUserSessionDO;
 import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
@@ -50,7 +44,6 @@ import cn.iocoder.dashboard.modules.system.service.logger.impl.SysLoginLogServic
 import cn.iocoder.dashboard.modules.system.service.user.SysUserServiceImpl;
 import cn.iocoder.dashboard.util.AssertUtils;
 import cn.iocoder.dashboard.util.RandomUtils;
-import cn.iocoder.dashboard.util.json.JsonUtils;
 import cn.iocoder.dashboard.util.object.ObjectUtils;
 
 /**
@@ -167,12 +160,12 @@ public class SysUserSessionServiceImplTest extends BaseDbAndRedisUnitTest {
         String userIp = randomString();
         SysUserDO dbUser1 = randomPojo(SysUserDO.class, o -> {
             o.setUsername("testUsername1");
-            o.setSex(randomEle(SysSexEnum.values()).getSEX());
+            o.setSex(randomEle(SysSexEnum.values()).getSex());
             o.setStatus(CommonStatusEnum.ENABLE.getStatus());
         });
         SysUserDO dbUser2 = randomPojo(SysUserDO.class, o -> {
             o.setUsername("testUsername2");
-            o.setSex(randomEle(SysSexEnum.values()).getSEX());
+            o.setSex(randomEle(SysSexEnum.values()).getSex());
             o.setStatus(CommonStatusEnum.ENABLE.getStatus());
         });
         SysUserSessionDO dbSession = randomPojo(SysUserSessionDO.class, o -> {

+ 2 - 2
src/test/java/cn/iocoder/dashboard/modules/system/service/logger/SysOperateLogServiceImplTest.java

@@ -68,7 +68,7 @@ public class SysOperateLogServiceImplTest extends BaseDbUnitTest {
         // 先构造用户
         SysUserDO user = RandomUtils.randomPojo(SysUserDO.class, o -> {
             o.setNickname("wangkai");
-            o.setSex(SysSexEnum.MALE.getSEX());
+            o.setSex(SysSexEnum.MALE.getSex());
             o.setStatus(CommonStatusEnum.ENABLE.getStatus());
         });
         when(userService.getUsersByNickname("wangkai")).thenReturn(Collections.singletonList(user));
@@ -119,7 +119,7 @@ public class SysOperateLogServiceImplTest extends BaseDbUnitTest {
         // 先构造用户
         SysUserDO user = RandomUtils.randomPojo(SysUserDO.class, o -> {
             o.setNickname("wangkai");
-            o.setSex(SysSexEnum.MALE.getSEX());
+            o.setSex(SysSexEnum.MALE.getSex());
             o.setStatus(CommonStatusEnum.ENABLE.getStatus());
         });
         when(userService.getUsersByNickname("wangkai")).thenReturn(Collections.singletonList(user));

+ 1 - 1
src/test/java/cn/iocoder/dashboard/modules/system/service/sms/SysSmsChannelServiceTest.java

@@ -56,7 +56,7 @@ public class SysSmsChannelServiceTest extends BaseDbUnitTest {
 
     @Test
     public void testInitLocalCache_success() {
-        // mock 数据s
+        // mock 数据
         SysSmsChannelDO smsChannelDO01 = randomSmsChannelDO();
         smsChannelMapper.insert(smsChannelDO01);
         SysSmsChannelDO smsChannelDO02 = randomSmsChannelDO();

+ 590 - 0
src/test/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImplTest.java

@@ -0,0 +1,590 @@
+package cn.iocoder.dashboard.modules.system.service.user;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.iocoder.dashboard.BaseDbUnitTest;
+import cn.iocoder.dashboard.common.enums.CommonStatusEnum;
+import cn.iocoder.dashboard.common.pojo.PageResult;
+import cn.iocoder.dashboard.modules.infra.service.file.InfFileService;
+import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdatePasswordReqVO;
+import cn.iocoder.dashboard.modules.system.controller.user.vo.profile.SysUserProfileUpdateReqVO;
+import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*;
+import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysDeptDO;
+import cn.iocoder.dashboard.modules.system.dal.dataobject.dept.SysPostDO;
+import cn.iocoder.dashboard.modules.system.dal.dataobject.user.SysUserDO;
+import cn.iocoder.dashboard.modules.system.dal.mysql.user.SysUserMapper;
+import cn.iocoder.dashboard.modules.system.enums.common.SysSexEnum;
+import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService;
+import cn.iocoder.dashboard.modules.system.service.dept.SysPostService;
+import cn.iocoder.dashboard.modules.system.service.permission.SysPermissionService;
+import cn.iocoder.dashboard.util.collection.ArrayUtils;
+import cn.iocoder.dashboard.util.collection.CollectionUtils;
+import cn.iocoder.dashboard.util.object.ObjectUtils;
+import org.junit.jupiter.api.Test;
+import org.mockito.stubbing.Answer;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Import;
+import org.springframework.security.crypto.password.PasswordEncoder;
+
+import javax.annotation.Resource;
+import java.io.ByteArrayInputStream;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+import static cn.hutool.core.util.RandomUtil.randomBytes;
+import static cn.hutool.core.util.RandomUtil.randomEle;
+import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
+import static cn.iocoder.dashboard.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.dashboard.util.AssertUtils.assertServiceException;
+import static cn.iocoder.dashboard.util.RandomUtils.*;
+import static cn.iocoder.dashboard.util.date.DateUtils.buildTime;
+import static org.assertj.core.util.Lists.newArrayList;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * {@link SysUserService} 的单元测试类
+ *
+ * @author zxl
+ */
+@Import(SysUserServiceImpl.class)
+public class SysUserServiceImplTest extends BaseDbUnitTest {
+
+    @Resource
+    private SysUserServiceImpl userService;
+
+    @Resource
+    private SysUserMapper userMapper;
+
+    @MockBean
+    private SysDeptService deptService;
+    @MockBean
+    private SysPostService postService;
+    @MockBean
+    private SysPermissionService permissionService;
+    @MockBean
+    private PasswordEncoder passwordEncoder;
+    @MockBean
+    private InfFileService fileService;
+
+    @Test
+    public void testCreatUser_success() {
+        // 准备参数
+        SysUserCreateReqVO reqVO = randomPojo(SysUserCreateReqVO.class, o -> {
+            o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex());
+            o.setMobile(randomString());
+        });
+        // mock deptService 的方法
+        SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
+            o.setId(reqVO.getDeptId());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
+        // mock postService 的方法
+        List<SysPostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
+                randomPojo(SysPostDO.class, o -> {
+                    o.setId(postId);
+                    o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+                }));
+        when(postService.getPosts(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
+        // mock passwordEncoder 的方法
+        when(passwordEncoder.encode(eq(reqVO.getPassword()))).thenReturn("yudaoyuanma");
+
+        // 调用
+        Long userId = userService.createUser(reqVO);
+        // 断言
+        SysUserDO user = userMapper.selectById(userId);
+        assertPojoEquals(reqVO, user, "password");
+        assertEquals("yudaoyuanma", user.getPassword());
+        assertEquals(CommonStatusEnum.ENABLE.getStatus(), user.getStatus());
+    }
+
+    @Test
+    public void testUpdateUser_success() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        SysUserUpdateReqVO reqVO = randomPojo(SysUserUpdateReqVO.class, o -> {
+            o.setId(dbUser.getId());
+            o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex());
+            o.setMobile(randomString());
+        });
+        // mock deptService 的方法
+        SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
+            o.setId(reqVO.getDeptId());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
+        // mock postService 的方法
+        List<SysPostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->
+                randomPojo(SysPostDO.class, o -> {
+                    o.setId(postId);
+                    o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+                }));
+        when(postService.getPosts(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);
+
+        // 调用
+        userService.updateUser(reqVO);
+        // 断言
+        SysUserDO user = userMapper.selectById(reqVO.getId());
+        assertPojoEquals(reqVO, user);
+    }
+
+    @Test
+    public void testUpdateUserProfile_success() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        Long userId = dbUser.getId();
+        SysUserProfileUpdateReqVO reqVO = randomPojo(SysUserProfileUpdateReqVO.class, o -> {
+            o.setMobile(randomString());
+            o.setSex(RandomUtil.randomEle(SysSexEnum.values()).getSex());
+        });
+
+        // 调用
+        userService.updateUserProfile(userId, reqVO);
+        // 断言
+        SysUserDO user = userMapper.selectById(userId);
+        assertPojoEquals(reqVO, user);
+    }
+
+    @Test
+    public void testUpdateUserPassword_success() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO(o -> o.setPassword("encode:yudao"));
+        userMapper.insert(dbUser);
+        // 准备参数
+        Long userId = dbUser.getId();
+        SysUserProfileUpdatePasswordReqVO reqVO = randomPojo(SysUserProfileUpdatePasswordReqVO.class, o -> {
+            o.setOldPassword("yudao");
+            o.setNewPassword("yuanma");
+        });
+        // mock 方法
+        when(passwordEncoder.encode(anyString())).then(
+                (Answer<String>) invocationOnMock -> "encode:" + invocationOnMock.getArgument(0));
+        when(passwordEncoder.matches(eq(reqVO.getOldPassword()), eq(dbUser.getPassword()))).thenReturn(true);
+
+        // 调用
+        userService.updateUserPassword(userId, reqVO);
+        // 断言
+        SysUserDO user = userMapper.selectById(userId);
+        assertEquals("encode:yuanma", user.getPassword());
+    }
+
+    @Test
+    public void testUpdateUserAvatar_success() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        Long userId = dbUser.getId();
+        byte[] avatarFileBytes = randomBytes(10);
+        ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes);
+        // mock 方法
+        String avatar = randomString();
+        when(fileService.createFile(anyString(), eq(avatarFileBytes))).thenReturn(avatar);
+
+        // 调用
+        userService.updateUserAvatar(userId, avatarFile);
+        // 断言
+        SysUserDO user = userMapper.selectById(userId);
+        assertEquals(avatar, user.getAvatar());
+    }
+
+    @Test
+    public void testUpdateUserPassword02_success() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        Long userId = dbUser.getId();
+        String password = "yudao";
+        // mock 方法
+        when(passwordEncoder.encode(anyString())).then(
+                (Answer<String>) invocationOnMock -> "encode:" + invocationOnMock.getArgument(0));
+
+        // 调用
+        userService.updateUserPassword(userId, password);
+        // 断言
+        SysUserDO user = userMapper.selectById(userId);
+        assertEquals("encode:" + password, user.getPassword());
+    }
+
+    @Test
+    public void testUpdateUserStatus() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        Long userId = dbUser.getId();
+        Integer status = randomCommonStatus();
+
+        // 调用
+        userService.updateUserStatus(userId, status);
+        // 断言
+        SysUserDO user = userMapper.selectById(userId);
+        assertEquals(status, user.getStatus());
+    }
+
+    @Test
+    public void testDeleteUser_success(){
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        Long userId = dbUser.getId();
+
+        // 调用数据
+        userService.deleteUser(userId);
+        // 校验结果
+        assertNull(userService.getUser(userId));
+        // 校验调用次数
+        verify(permissionService, times(1)).processUserDeleted(eq(userId));
+    }
+
+    @Test
+    public void testGetUserPage() {
+        // mock 数据
+        SysUserDO dbUser = initGetUserPageData();
+        // 准备参数
+        SysUserPageReqVO reqVO = new SysUserPageReqVO();
+        reqVO.setUsername("yudao");
+        reqVO.setMobile("1560");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setBeginTime(buildTime(2020, 12, 1));
+        reqVO.setEndTime(buildTime(2020, 12, 24));
+        reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
+        // mock 方法
+        List<SysDeptDO> deptList = newArrayList(randomPojo(SysDeptDO.class, o -> o.setId(2L)));
+        when(deptService.getDeptsByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
+
+        // 调用
+        PageResult<SysUserDO> pageResult = userService.getUserPage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbUser, pageResult.getList().get(0));
+    }
+
+    @Test
+    public void testGetUsers() {
+        // mock 数据
+        SysUserDO dbUser = initGetUserPageData();
+        // 准备参数
+        SysUserExportReqVO reqVO = new SysUserExportReqVO();
+        reqVO.setUsername("yudao");
+        reqVO.setMobile("1560");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setBeginTime(buildTime(2020, 12, 1));
+        reqVO.setEndTime(buildTime(2020, 12, 24));
+        reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
+        // mock 方法
+        List<SysDeptDO> deptList = newArrayList(randomPojo(SysDeptDO.class, o -> o.setId(2L)));
+        when(deptService.getDeptsByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
+
+        // 调用
+        List<SysUserDO> list = userService.getUsers(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbUser, list.get(0));
+    }
+
+    /**
+     * 初始化 getUserPage 方法的测试数据
+     */
+    private SysUserDO initGetUserPageData() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO(o -> { // 等会查询到
+            o.setUsername("yudaoyuanma");
+            o.setMobile("15601691300");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCreateTime(buildTime(2020, 12, 12));
+            o.setDeptId(2L);
+        });
+        userMapper.insert(dbUser);
+        // 测试 username 不匹配
+        userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setUsername("yuanma")));
+        // 测试 mobile 不匹配
+        userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setMobile("18818260888")));
+        // 测试 status 不匹配
+        userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
+        // 测试 createTime 不匹配
+        userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setCreateTime(buildTime(2020, 11, 11))));
+        // 测试 dept 不匹配
+        userMapper.insert(ObjectUtils.clone(dbUser, o -> o.setDeptId(0L)));
+        return dbUser;
+    }
+
+    /**
+     * 情况一,校验不通过,导致插入失败
+     */
+    @Test
+    public void testImportUsers_01() {
+        // 准备参数
+        SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class);
+        // mock 方法
+
+        // 调用
+        SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true);
+        // 断言
+        assertEquals(0, respVO.getCreateUsernames().size());
+        assertEquals(0, respVO.getUpdateUsernames().size());
+        assertEquals(1, respVO.getFailureUsernames().size());
+        assertEquals(DEPT_NOT_FOUND.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername()));
+    }
+
+    /**
+     * 情况二,不存在,进行插入
+     */
+    @Test
+    public void testImportUsers_02() {
+        // 准备参数
+        SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> {
+            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
+            o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
+        });
+        // mock deptService 的方法
+        SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
+            o.setId(importUser.getDeptId());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
+        // mock passwordEncoder 的方法
+        when(passwordEncoder.encode(eq("yudaoyuanma"))).thenReturn("java");
+
+        // 调用
+        SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true);
+        // 断言
+        assertEquals(1, respVO.getCreateUsernames().size());
+        SysUserDO user = userMapper.selectByUsername(respVO.getCreateUsernames().get(0));
+        assertPojoEquals(importUser, user);
+        assertEquals("java", user.getPassword());
+        assertEquals(0, respVO.getUpdateUsernames().size());
+        assertEquals(0, respVO.getFailureUsernames().size());
+    }
+
+    /**
+     * 情况三,存在,但是不强制更新
+     */
+    @Test
+    public void testImportUsers_03() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> {
+            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
+            o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
+            o.setUsername(dbUser.getUsername());
+        });
+        // mock deptService 的方法
+        SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
+            o.setId(importUser.getDeptId());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
+
+        // 调用
+        SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), false);
+        // 断言
+        assertEquals(0, respVO.getCreateUsernames().size());
+        assertEquals(0, respVO.getUpdateUsernames().size());
+        assertEquals(1, respVO.getFailureUsernames().size());
+        assertEquals(USER_USERNAME_EXISTS.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername()));
+    }
+
+    /**
+     * 情况四,存在,强制更新
+     */
+    @Test
+    public void testImportUsers_04() {
+        // mock 数据
+        SysUserDO dbUser = randomSysUserDO();
+        userMapper.insert(dbUser);
+        // 准备参数
+        SysUserImportExcelVO importUser = randomPojo(SysUserImportExcelVO.class, o -> {
+            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
+            o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
+            o.setUsername(dbUser.getUsername());
+        });
+        // mock deptService 的方法
+        SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
+            o.setId(importUser.getDeptId());
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        });
+        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
+
+        // 调用
+        SysUserImportRespVO respVO = userService.importUsers(newArrayList(importUser), true);
+        // 断言
+        assertEquals(0, respVO.getCreateUsernames().size());
+        assertEquals(1, respVO.getUpdateUsernames().size());
+        SysUserDO user = userMapper.selectByUsername(respVO.getUpdateUsernames().get(0));
+        assertPojoEquals(importUser, user);
+        assertEquals(0, respVO.getFailureUsernames().size());
+    }
+
+    @Test
+    public void testCheckUserExists_notExists() {
+        assertServiceException(() -> userService.checkUserExists(randomLongId()), USER_NOT_EXISTS);
+    }
+
+    @Test
+    public void testCheckUsernameUnique_usernameExistsForCreate() {
+        // 准备参数
+        String username = randomString();
+        // mock 数据
+        userMapper.insert(randomSysUserDO(o -> o.setUsername(username)));
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkUsernameUnique(null, username),
+                USER_USERNAME_EXISTS);
+    }
+
+    @Test
+    public void testCheckUsernameUnique_usernameExistsForUpdate() {
+        // 准备参数
+        Long id = randomLongId();
+        String username = randomString();
+        // mock 数据
+        userMapper.insert(randomSysUserDO(o -> o.setUsername(username)));
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkUsernameUnique(id, username),
+                USER_USERNAME_EXISTS);
+    }
+
+    @Test
+    public void testCheckEmailUnique_emailExistsForCreate() {
+        // 准备参数
+        String email = randomString();
+        // mock 数据
+        userMapper.insert(randomSysUserDO(o -> o.setEmail(email)));
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkEmailUnique(null, email),
+                USER_EMAIL_EXISTS);
+    }
+
+    @Test
+    public void testCheckEmailUnique_emailExistsForUpdate() {
+        // 准备参数
+        Long id = randomLongId();
+        String email = randomString();
+        // mock 数据
+        userMapper.insert(randomSysUserDO(o -> o.setEmail(email)));
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkEmailUnique(id, email),
+                USER_EMAIL_EXISTS);
+    }
+
+    @Test
+    public void testCheckMobileUnique_mobileExistsForCreate() {
+        // 准备参数
+        String mobile = randomString();
+        // mock 数据
+        userMapper.insert(randomSysUserDO(o -> o.setMobile(mobile)));
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkMobileUnique(null, mobile),
+                USER_MOBILE_EXISTS);
+    }
+
+    @Test
+    public void testCheckMobileUnique_mobileExistsForUpdate() {
+        // 准备参数
+        Long id = randomLongId();
+        String mobile = randomString();
+        // mock 数据
+        userMapper.insert(randomSysUserDO(o -> o.setMobile(mobile)));
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkMobileUnique(id, mobile),
+                USER_MOBILE_EXISTS);
+    }
+
+    @Test
+    public void testCheckDeptEnable_notFound() {
+        assertServiceException(() -> userService.checkDeptEnable(randomLongId()),
+                DEPT_NOT_FOUND);
+    }
+
+    @Test
+    public void testCheckDeptEnable_notEnable() {
+        // 准备参数
+        Long deptId = randomLongId();
+        // mock deptService 的方法
+        SysDeptDO dept = randomPojo(SysDeptDO.class, o -> {
+            o.setId(deptId);
+            o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+        });
+        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkDeptEnable(deptId),
+                DEPT_NOT_ENABLE);
+    }
+
+    @Test
+    public void testCheckPostEnable_notFound() {
+        assertServiceException(() -> userService.checkPostEnable(randomSet(Long.class)),
+                POST_NOT_FOUND);
+    }
+
+    @Test
+    public void testCheckPostEnable_notEnable() {
+        // 准备参数
+        Set<Long> postIds = randomSet(Long.class);
+        // mock postService 的方法
+        List<SysPostDO> posts = CollectionUtils.convertList(postIds, postId ->
+                randomPojo(SysPostDO.class, o -> {
+                    o.setId(postId);
+                    o.setStatus(CommonStatusEnum.DISABLE.getStatus());
+                }));
+        when(postService.getPosts(eq(postIds), isNull())).thenReturn(posts);
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkPostEnable(postIds),
+                POST_NOT_ENABLE, CollUtil.getFirst(posts).getName());
+    }
+
+    @Test
+    public void testCheckOldPassword_notExists() {
+        assertServiceException(() -> userService.checkOldPassword(randomLongId(), randomString()),
+                USER_NOT_EXISTS);
+    }
+
+    @Test
+    public void testCheckOldPassword_passwordFailed() {
+        // mock 数据
+        SysUserDO user = randomSysUserDO();
+        userMapper.insert(user);
+        // 准备参数
+        Long id = user.getId();
+        String oldPassword = user.getPassword();
+
+        // 调用,校验异常
+        assertServiceException(() -> userService.checkOldPassword(id, oldPassword),
+                USER_PASSWORD_FAILED);
+        // 校验调用
+        verify(passwordEncoder, times(1)).matches(eq(oldPassword), eq(user.getPassword()));
+    }
+
+    // ========== 随机对象 ==========
+
+    @SafeVarargs
+    private static SysUserDO randomSysUserDO(Consumer<SysUserDO>... consumers) {
+        Consumer<SysUserDO> consumer = (o) -> {
+            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
+            o.setSex(randomEle(SysSexEnum.values()).getSex()); // 保证 sex 的范围
+        };
+        return randomPojo(SysUserDO.class, ArrayUtils.append(consumer, consumers));
+    }
+
+}