瀏覽代碼

完善 OAuth2OpenControllerTest 单元测试

YunaiV 2 年之前
父節點
當前提交
495f121463

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-test/src/main/java/cn/iocoder/yudao/framework/test/core/util/RandomUtils.java

@@ -45,7 +45,7 @@ public class RandomUtils {
             }
             // 如果是 type、status 结尾的字段,返回 tinyint 范围
             if (StrUtil.endWithAnyIgnoreCase(attributeMetadata.getAttributeName(),
-                    "type", "status", "category")) {
+                    "type", "status", "category", "scope")) {
                 return RandomUtil.randomInt(0, TINYINT_MAX + 1);
             }
             return RandomUtil.randomInt();

+ 3 - 7
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java

@@ -52,8 +52,6 @@ import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUti
  * 考虑到【本系统】暂时不想做的过于复杂,默认只有获取到 access token 之后,可以访问【本系统】管理后台的 /system-api/* 所有接口,除非手动添加 scope 控制。
  * scope 的使用示例,可见 {@link OAuth2UserController} 类
  *
- *
- *
  * @author 芋道源码
  */
 @Api(tags = "管理后台 - OAuth2.0 授权")
@@ -185,8 +183,7 @@ public class OAuth2OpenController {
         // 0. 校验用户已经登录。通过 Spring Security 实现
 
         // 1. 获得 Client 客户端的信息
-        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null,
-                null, null, null);
+        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId);
         // 2. 获得用户已经授权的信息
         List<OAuth2ApproveDO> approves = oauth2ApproveService.getApproveList(getLoginUserId(), getUserType(), clientId);
         // 拼接返回
@@ -223,7 +220,6 @@ public class OAuth2OpenController {
         @SuppressWarnings("unchecked")
         Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class);
         scopes = ObjectUtil.defaultIfNull(scopes, Collections.emptyMap());
-        // TODO 芋艿:针对 approved + scopes 在看看 spring security 的实现
         // 0. 校验用户已经登录。通过 Spring Security 实现
 
         // 1.1 校验 responseType 是否满足 code 或者 token 值
@@ -262,7 +258,7 @@ public class OAuth2OpenController {
         if (StrUtil.equalsAny(responseType, "token")) {
             return OAuth2GrantTypeEnum.IMPLICIT;
         }
-        throw exception0(BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token");
+        throw exception0(BAD_REQUEST.getCode(), "response_type 参数值允许 code 和 token");
     }
 
     private String getImplicitGrantRedirect(Long userId, OAuth2ClientDO client,
@@ -279,7 +275,7 @@ public class OAuth2OpenController {
     private String getAuthorizationCodeRedirect(Long userId, OAuth2ClientDO client,
                                                 List<String> scopes, String redirectUri, String state) {
         // 1. 创建 code 授权码
-        String authorizationCode = oauth2GrantService.grantAuthorizationCodeForCode(userId,getUserType(), client.getClientId(), scopes,
+        String authorizationCode = oauth2GrantService.grantAuthorizationCodeForCode(userId, getUserType(), client.getClientId(), scopes,
                 redirectUri, state);
         // 2. 拼接重定向的 URL
         return OAuth2Utils.buildAuthorizationCodeRedirectUri(redirectUri, authorizationCode, state);

+ 3 - 3
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java

@@ -7,7 +7,7 @@ import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
-import java.util.Set;
+import java.util.List;
 
 @ApiModel("管理后台 - 【开放接口】校验令牌 Response VO")
 @Data
@@ -28,13 +28,13 @@ public class OAuth2OpenCheckTokenRespVO {
     @ApiModelProperty(value = "客户端编号", required = true, example = "car")
     private String clientId;
     @ApiModelProperty(value = "授权范围", required = true, example = "user_info")
-    private Set<String> scopes;
+    private List<String> scopes;
 
     @ApiModelProperty(value = "访问令牌", required = true, example = "tudou")
     @JsonProperty("access_token")
     private String accessToken;
 
     @ApiModelProperty(value = "过期时间", required = true, example = "1593092157", notes = "时间戳 / 1000,即单位:秒")
-    @JsonProperty("exp")
     private Long exp;
+
 }

+ 7 - 5
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/util/oauth2/OAuth2Utils.java

@@ -61,11 +61,13 @@ public class OAuth2Utils {
         if (CollUtil.isNotEmpty(scopes)) {
             vars.put("scope", buildScopeStr(scopes));
         }
-        for (String key : additionalInformation.keySet()) {
-            Object value = additionalInformation.get(key);
-            if (value != null) {
-                keys.put("extra_" + key, key);
-                vars.put("extra_" + key, value);
+        if (CollUtil.isNotEmpty(additionalInformation)) {
+            for (String key : additionalInformation.keySet()) {
+                Object value = additionalInformation.get(key);
+                if (value != null) {
+                    keys.put("extra_" + key, key);
+                    vars.put("extra_" + key, value);
+                }
             }
         }
         // Do not include the refresh token (even if there is one)

+ 330 - 0
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenControllerTest.java

@@ -0,0 +1,330 @@
+package cn.iocoder.yudao.module.system.controller.admin.oauth2;
+
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.iocoder.yudao.framework.common.core.KeyValue;
+import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
+import cn.iocoder.yudao.module.system.enums.auth.OAuth2GrantTypeEnum;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ApproveService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService;
+import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
+import org.assertj.core.util.Lists;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+import javax.servlet.http.HttpServletRequest;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.addTime;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
+import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
+import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * {@link OAuth2OpenController} 的单元测试
+ *
+ * @author 芋道源码
+ */
+public class OAuth2OpenControllerTest extends BaseMockitoUnitTest {
+
+    @InjectMocks
+    private OAuth2OpenController oauth2OpenController;
+
+    @Mock
+    private OAuth2GrantService oauth2GrantService;
+    @Mock
+    private OAuth2ClientService oauth2ClientService;
+    @Mock
+    private OAuth2ApproveService oauth2ApproveService;
+    @Mock
+    private OAuth2TokenService oauth2TokenService;
+
+    @Test
+    public void testPostAccessToken_authorizationCode() {
+        // 准备参数
+        String granType = OAuth2GrantTypeEnum.AUTHORIZATION_CODE.getGrantType();
+        String code = randomString();
+        String redirectUri = randomString();
+        String state = randomString();
+        HttpServletRequest request = mockRequest("test_client_id", "test_client_secret");
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("test_client_id");
+        when(oauth2ClientService.validOAuthClientFromCache(eq("test_client_id"), eq("test_client_secret"), eq(granType), eq(new ArrayList<>()), eq(redirectUri))).thenReturn(client);
+
+        // mock 方法(访问令牌)
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
+                .setExpiresTime(addTime(Duration.ofMillis(30010L))); // 多给 10 毫秒,保证可执行完
+        when(oauth2GrantService.grantAuthorizationCodeForAccessToken(eq("test_client_id"),
+                eq(code), eq(redirectUri), eq(state))).thenReturn(accessTokenDO);
+
+        // 调用
+        CommonResult<OAuth2OpenAccessTokenRespVO> result = oauth2OpenController.postAccessToken(request, granType,
+                code, redirectUri, state, null, null, null, null);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertPojoEquals(accessTokenDO, result.getData());
+        assertEquals(30L, result.getData().getExpiresIn()); // 执行过程会过去几毫秒
+    }
+
+    @Test
+    public void testPostAccessToken_password() {
+        // 准备参数
+        String granType = OAuth2GrantTypeEnum.PASSWORD.getGrantType();
+        String username = randomString();
+        String password = randomString();
+        String scope = "write read";
+        HttpServletRequest request = mockRequest("test_client_id", "test_client_secret");
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("test_client_id");
+        when(oauth2ClientService.validOAuthClientFromCache(eq("test_client_id"), eq("test_client_secret"),
+                eq(granType), eq(Lists.newArrayList("write", "read")), isNull())).thenReturn(client);
+
+        // mock 方法(访问令牌)
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
+                .setExpiresTime(addTime(Duration.ofMillis(30010L))); // 多给 10 毫秒,保证可执行完
+        when(oauth2GrantService.grantPassword(eq(username), eq(password), eq("test_client_id"),
+                eq(Lists.newArrayList("write", "read")))).thenReturn(accessTokenDO);
+
+        // 调用
+        CommonResult<OAuth2OpenAccessTokenRespVO> result = oauth2OpenController.postAccessToken(request, granType,
+                null, null, null, username, password, scope, null);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertPojoEquals(accessTokenDO, result.getData());
+        assertEquals(30L, result.getData().getExpiresIn()); // 执行过程会过去几毫秒
+    }
+
+    @Test
+    public void testPostAccessToken_refreshToken() {
+        // 准备参数
+        String granType = OAuth2GrantTypeEnum.REFRESH_TOKEN.getGrantType();
+        String refreshToken = randomString();
+        String password = randomString();
+        HttpServletRequest request = mockRequest("test_client_id", "test_client_secret");
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("test_client_id");
+        when(oauth2ClientService.validOAuthClientFromCache(eq("test_client_id"), eq("test_client_secret"),
+                eq(granType), eq(Lists.newArrayList()), isNull())).thenReturn(client);
+
+        // mock 方法(访问令牌)
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
+                .setExpiresTime(addTime(Duration.ofMillis(30010L))); // 多给 10 毫秒,保证可执行完
+        when(oauth2GrantService.grantRefreshToken(eq(refreshToken), eq("test_client_id"))).thenReturn(accessTokenDO);
+
+        // 调用
+        CommonResult<OAuth2OpenAccessTokenRespVO> result = oauth2OpenController.postAccessToken(request, granType,
+                null, null, null, null, password, null, refreshToken);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertPojoEquals(accessTokenDO, result.getData());
+        assertEquals(30L, result.getData().getExpiresIn()); // 执行过程会过去几毫秒
+    }
+
+    @Test
+    public void testPostAccessToken_implicit() {
+        // 调用,并断言
+        assertServiceException(() -> oauth2OpenController.postAccessToken(null,
+                        OAuth2GrantTypeEnum.IMPLICIT.getGrantType(), null, null, null,
+                        null, null, null, null),
+                new ErrorCode(400, "Token 接口不支持 implicit 授权模式"));
+    }
+
+    @Test
+    public void testRevokeToken() {
+        // 准备参数
+        HttpServletRequest request = mockRequest("demo_client_id", "demo_client_secret");
+        String token = randomString();
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("demo_client_id");
+        when(oauth2ClientService.validOAuthClientFromCache(eq("demo_client_id"),
+                eq("demo_client_secret"), isNull(), isNull(), isNull())).thenReturn(client);
+        // mock 方法(移除)
+        when(oauth2GrantService.revokeToken(eq("demo_client_id"), eq(token))).thenReturn(true);
+
+        // 调用
+        CommonResult<Boolean> result = oauth2OpenController.revokeToken(request, token);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertTrue(result.getData());
+    }
+
+    @Test
+    public void testCheckToken() {
+        // 准备参数
+        HttpServletRequest request = mockRequest("demo_client_id", "demo_client_secret");
+        String token = randomString();
+        // mock 方法
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setUserType(UserTypeEnum.ADMIN.getValue()).setExpiresTime(new Date(1653485731195L));
+        when(oauth2TokenService.checkAccessToken(eq(token))).thenReturn(accessTokenDO);
+
+        // 调用
+        CommonResult<OAuth2OpenCheckTokenRespVO> result = oauth2OpenController.checkToken(request, token);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertPojoEquals(accessTokenDO, result.getData());
+        assertEquals(1653485731L, result.getData().getExp()); // 执行过程会过去几毫秒
+    }
+
+    @Test
+    public void testAuthorize() {
+        // 准备参数
+        String clientId = randomString();
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("demo_client_id").setScopes(ListUtil.toList("read", "write", "all"));
+        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(client);
+        // mock 方法(approve)
+        List<OAuth2ApproveDO> approves = asList(
+                randomPojo(OAuth2ApproveDO.class).setScope("read").setApproved(true),
+                randomPojo(OAuth2ApproveDO.class).setScope("write").setApproved(false));
+        when(oauth2ApproveService.getApproveList(isNull(), eq(UserTypeEnum.ADMIN.getValue()), eq(clientId))).thenReturn(approves);
+
+        // 调用
+        CommonResult<OAuth2OpenAuthorizeInfoRespVO> result = oauth2OpenController.authorize(clientId);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertPojoEquals(client, result.getData().getClient());
+        assertEquals(new KeyValue<>("read", true), result.getData().getScopes().get(0));
+        assertEquals(new KeyValue<>("write", false), result.getData().getScopes().get(1));
+        assertEquals(new KeyValue<>("all", false), result.getData().getScopes().get(2));
+    }
+
+    @Test
+    public void testApproveOrDeny_grantTypeError() {
+        // 调用,并断言
+        assertServiceException(() -> oauth2OpenController.approveOrDeny(randomString(), null,
+                        null, null, null, null),
+                new ErrorCode(400, "response_type 参数值只允许 code 和 token"));
+    }
+
+    @Test // autoApprove = true,但是不通过
+    public void testApproveOrDeny_autoApproveNo() {
+        // 准备参数
+        String responseType = "code";
+        String clientId = randomString();
+        String scope = "{\"read\": true, \"write\": false}";
+        String redirectUri = randomString();
+        String state = randomString();
+        // mock 方法
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class);
+        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq("authorization_code"),
+                eq(asSet("read", "write")), eq(redirectUri))).thenReturn(client);
+
+        // 调用
+        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,
+                scope, redirectUri, true, state);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertNull(result.getData());
+    }
+
+    @Test // autoApprove = false,但是不通过
+    public void testApproveOrDeny_ApproveNo() {
+        // 准备参数
+        String responseType = "token";
+        String clientId = randomString();
+        String scope = "{\"read\": true, \"write\": false}";
+        String redirectUri = "https://www.iocoder.cn";
+        String state = "test";
+        // mock 方法
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class);
+        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq("implicit"),
+                eq(asSet("read", "write")), eq(redirectUri))).thenReturn(client);
+
+        // 调用
+        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,
+                scope, redirectUri, false, state);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertEquals("https://www.iocoder.cn#error=access_denied&error_description=User%20denied%20access&state=test", result.getData());
+    }
+
+    @Test // autoApprove = true,通过 + token
+    public void testApproveOrDeny_autoApproveWithToken() {
+        // 准备参数
+        String responseType = "token";
+        String clientId = randomString();
+        String scope = "{\"read\": true, \"write\": false}";
+        String redirectUri = "https://www.iocoder.cn";
+        String state = "test";
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(clientId).setAdditionalInformation(null);
+        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq("implicit"),
+                eq(asSet("read", "write")), eq(redirectUri))).thenReturn(client);
+        // mock 方法(场景一)
+        when(oauth2ApproveService.checkForPreApproval(isNull(), eq(UserTypeEnum.ADMIN.getValue()),
+                eq(clientId), eq(SetUtils.asSet("read", "write")))).thenReturn(true);
+        // mock 方法(访问令牌)
+        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)
+                .setAccessToken("test_access_token").setExpiresTime(addTime(Duration.ofMillis(30010L)));
+        when(oauth2GrantService.grantImplicit(isNull(), eq(UserTypeEnum.ADMIN.getValue()),
+                eq(clientId), eq(ListUtil.toList("read")))).thenReturn(accessTokenDO);
+
+        // 调用
+        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,
+                scope, redirectUri, true, state);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertEquals("https://www.iocoder.cn#access_token=test_access_token&token_type=bearer&state=test&expires_in=30&scope=read", result.getData());
+    }
+
+    @Test // autoApprove = false,通过 + code
+    public void testApproveOrDeny_approveWithCode() {
+        // 准备参数
+        String responseType = "code";
+        String clientId = randomString();
+        String scope = "{\"read\": true, \"write\": false}";
+        String redirectUri = "https://www.iocoder.cn";
+        String state = "test";
+        // mock 方法(client)
+        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(clientId).setAdditionalInformation(null);
+        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq("authorization_code"),
+                eq(asSet("read", "write")), eq(redirectUri))).thenReturn(client);
+        // mock 方法(场景二)
+        when(oauth2ApproveService.updateAfterApproval(isNull(), eq(UserTypeEnum.ADMIN.getValue()), eq(clientId),
+                eq(MapUtil.builder(new LinkedHashMap<String, Boolean>()).put("read", true).put("write", false).build())))
+                .thenReturn(true);
+        // mock 方法(访问令牌)
+        String authorizationCode = "test_code";
+        when(oauth2GrantService.grantAuthorizationCodeForCode(isNull(), eq(UserTypeEnum.ADMIN.getValue()),
+                eq(clientId), eq(ListUtil.toList("read")), eq(redirectUri), eq(state))).thenReturn(authorizationCode);
+
+        // 调用
+        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,
+                scope, redirectUri, false, state);
+        // 断言
+        assertEquals(0, result.getCode());
+        assertEquals("https://www.iocoder.cn?code=test_code&state=test", result.getData());
+    }
+
+    private HttpServletRequest mockRequest(String clientId, String secret) {
+        HttpServletRequest request = mock(HttpServletRequest.class);
+        when(request.getParameter(eq("client_id"))).thenReturn(clientId);
+        when(request.getParameter(eq("client_secret"))).thenReturn(secret);
+        return request;
+    }
+
+}

+ 60 - 52
yudao-module-system/yudao-module-system-biz/src/test/java/cn/iocoder/yudao/module/system/service/permission/RoleServiceTest.java

@@ -3,15 +3,17 @@ package cn.iocoder.yudao.module.system.service.permission;
 import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
+import cn.iocoder.yudao.framework.common.util.date.DateUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
 import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUpdateReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
 import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
+import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
 import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
 import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
-import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
 import org.junit.jupiter.api.Test;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.context.annotation.Import;
@@ -20,6 +22,7 @@ import javax.annotation.Resource;
 import java.util.*;
 import java.util.stream.Collectors;
 
+import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
 import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.max;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
 import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
@@ -155,61 +158,66 @@ public class RoleServiceTest extends BaseDbUnitTest {
     }
 
     @Test
-    public void testGetRoles_success() {
-        Map<Long, RoleDO> idRoleMap = new HashMap<>();
-        // 验证查询状态为1的角色
-        RoleDO roleDO1 = createRoleDO("role1", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 1);
-        roleMapper.insert(roleDO1);
-        idRoleMap.put(roleDO1.getId(), roleDO1);
-
-        RoleDO roleDO2 = createRoleDO("role2", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 1);
-        roleMapper.insert(roleDO2);
-        idRoleMap.put(roleDO2.getId(), roleDO2);
-
-        // 以下是排除的角色
-        RoleDO roleDO3 = createRoleDO("role3", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 2);
-        roleMapper.insert(roleDO3);
-
-        //调用
-        List<RoleDO> roles = roleService.getRoles(Arrays.asList(1));
-
-        //断言
-        assertEquals(2, roles.size());
-        roles.stream().forEach(r -> assertPojoEquals(idRoleMap.get(r.getId()), r));
+    public void testGetRoles() {
+        // mock 数据
+        RoleDO dbRole = randomPojo(RoleDO.class, o -> { // 等会查询到
+            o.setName("土豆");
+            o.setCode("tudou");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCreateTime(DateUtils.buildTime(2022, 2, 8));
+        });
+        roleMapper.insert(dbRole);
+        // 测试 name 不匹配
+        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setName("红薯")));
+        // 测试 code 不匹配
+        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCode("hong")));
+        // 测试 createTime 不匹配
+        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCreateTime(DateUtils.buildTime(2022, 2, 16))));
+        // 准备参数
+        RoleExportReqVO reqVO = new RoleExportReqVO();
+        reqVO.setName("土豆");
+        reqVO.setCode("tu");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setBeginTime(DateUtils.buildTime(2022, 2, 1));
+        reqVO.setEndTime(DateUtils.buildTime(2022, 2, 12));
 
+        // 调用
+        List<RoleDO> list = roleService.getRoleList(reqVO);
+        // 断言
+        assertEquals(1, list.size());
+        assertPojoEquals(dbRole, list.get(0));
     }
 
     @Test
-    public void testGetRolePage_success() {
-        Map<Long, RoleDO> idRoleMap = new HashMap<>();
-        // 验证名称包含"role", 状态为1,code为"code"的角色
-        // 第一页
-        RoleDO roleDO = createRoleDO("role1", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 1, "code");
-        roleMapper.insert(roleDO);
-        idRoleMap.put(roleDO.getId(), roleDO);
-        // 第二页
-        roleDO = createRoleDO("role2", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 1, "code");
-        roleMapper.insert(roleDO);
-
-        // 以下是排除的角色
-        roleDO = createRoleDO("role3", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 2, "code");
-        roleMapper.insert(roleDO);
-        roleDO = createRoleDO("role4", RoleTypeEnum.CUSTOM, DataScopeEnum.ALL, 1, "xxxxx");
-        roleMapper.insert(roleDO);
-
-        //调用
-        RolePageReqVO reqVO = randomPojo(RolePageReqVO.class, o -> {
-            o.setName("role");
-            o.setCode("code");
-            o.setStatus(1);
-            o.setPageNo(1);
-            o.setPageSize(1);
-            o.setBeginTime(null);
-            o.setEndTime(null);
+    public void testGetRolePage() {
+        // mock 数据
+        RoleDO dbRole = randomPojo(RoleDO.class, o -> { // 等会查询到
+            o.setName("土豆");
+            o.setCode("tudou");
+            o.setStatus(CommonStatusEnum.ENABLE.getStatus());
+            o.setCreateTime(DateUtils.buildTime(2022, 2, 8));
         });
-        PageResult<RoleDO> result = roleService.getRolePage(reqVO);
-        assertEquals(2, result.getTotal());
-        result.getList().stream().forEach(r -> assertPojoEquals(idRoleMap.get(r.getId()), r));
+        roleMapper.insert(dbRole);
+        // 测试 name 不匹配
+        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setName("红薯")));
+        // 测试 code 不匹配
+        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCode("hong")));
+        // 测试 createTime 不匹配
+        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCreateTime(DateUtils.buildTime(2022, 2, 16))));
+        // 准备参数
+        RolePageReqVO reqVO = new RolePageReqVO();
+        reqVO.setName("土豆");
+        reqVO.setCode("tu");
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setBeginTime(DateUtils.buildTime(2022, 2, 1));
+        reqVO.setEndTime(DateUtils.buildTime(2022, 2, 12));
+
+        // 调用
+        PageResult<RoleDO> pageResult = roleService.getRolePage(reqVO);
+        // 断言
+        assertEquals(1, pageResult.getTotal());
+        assertEquals(1, pageResult.getList().size());
+        assertPojoEquals(dbRole, pageResult.getList().get(0));
     }
 
     @Test