Commit 2334e177c535d30d7c0c800ac289feaab4a4e4ac

Authored by YunaiV
1 parent 3fbd3946

1. 引入 mockito-inline

2. 优化【数据权限】的单元测试
yudao-dependencies/pom.xml
@@ -40,6 +40,7 @@ @@ -40,6 +40,7 @@
40 <!-- Test 测试相关 --> 40 <!-- Test 测试相关 -->
41 <podam.version>7.2.6.RELEASE</podam.version> 41 <podam.version>7.2.6.RELEASE</podam.version>
42 <jedis-mock.version>0.1.16</jedis-mock.version> 42 <jedis-mock.version>0.1.16</jedis-mock.version>
  43 + <mockito-inline.version>3.6.28</mockito-inline.version>
43 <!-- 工具类相关 --> 44 <!-- 工具类相关 -->
44 <lombok.version>1.18.20</lombok.version> 45 <lombok.version>1.18.20</lombok.version>
45 <mapstruct.version>1.4.1.Final</mapstruct.version> 46 <mapstruct.version>1.4.1.Final</mapstruct.version>
@@ -312,6 +313,13 @@ @@ -312,6 +313,13 @@
312 <groupId>cn.iocoder.boot</groupId> 313 <groupId>cn.iocoder.boot</groupId>
313 <artifactId>yudao-spring-boot-starter-test</artifactId> 314 <artifactId>yudao-spring-boot-starter-test</artifactId>
314 <version>${revision}</version> 315 <version>${revision}</version>
  316 + <scope>test</scope>
  317 + </dependency>
  318 +
  319 + <dependency>
  320 + <groupId>org.mockito</groupId>
  321 + <artifactId>mockito-inline</artifactId>
  322 + <version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->
315 </dependency> 323 </dependency>
316 324
317 <dependency> 325 <dependency>
@@ -323,6 +331,10 @@ @@ -323,6 +331,10 @@
323 <artifactId>asm</artifactId> 331 <artifactId>asm</artifactId>
324 <groupId>org.ow2.asm</groupId> 332 <groupId>org.ow2.asm</groupId>
325 </exclusion> 333 </exclusion>
  334 + <exclusion>
  335 + <groupId>org.mockito</groupId>
  336 + <artifactId>mockito-core</artifactId>
  337 + </exclusion>
326 </exclusions> 338 </exclusions>
327 </dependency> 339 </dependency>
328 340
yudao-framework/yudao-spring-boot-starter-data-permission/src/main/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptor.java
@@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; @@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
10 import com.baomidou.mybatisplus.core.toolkit.PluginUtils; 10 import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
11 import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport; 11 import com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;
12 import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; 12 import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;
  13 +import lombok.Getter;
13 import lombok.RequiredArgsConstructor; 14 import lombok.RequiredArgsConstructor;
14 import net.sf.jsqlparser.expression.*; 15 import net.sf.jsqlparser.expression.*;
15 import net.sf.jsqlparser.expression.operators.conditional.AndExpression; 16 import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
@@ -48,6 +49,7 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne @@ -48,6 +49,7 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne
48 49
49 private final DataPermissionRuleFactory ruleFactory; 50 private final DataPermissionRuleFactory ruleFactory;
50 51
  52 + @Getter
51 private final MappedStatementCache mappedStatementCache = new MappedStatementCache(); 53 private final MappedStatementCache mappedStatementCache = new MappedStatementCache();
52 54
53 @Override // SELECT 场景 55 @Override // SELECT 场景
@@ -442,13 +444,14 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne @@ -442,13 +444,14 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne
442 * 444 *
443 * @author 芋道源码 445 * @author 芋道源码
444 */ 446 */
445 - private static final class MappedStatementCache { 447 + static final class MappedStatementCache {
446 448
447 /** 449 /**
448 - * 无需重写的映射 450 + * 指定数据权限规则,对指定 MappedStatement 无需重写(不生效)的缓存
449 * 451 *
450 * value:{@link MappedStatement#getId()} 编号 452 * value:{@link MappedStatement#getId()} 编号
451 */ 453 */
  454 + @Getter
452 private final Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements = new ConcurrentHashMap<>(); 455 private final Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements = new ConcurrentHashMap<>();
453 456
454 /** 457 /**
@@ -467,7 +470,7 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne @@ -467,7 +470,7 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne
467 // 任一规则不在 noRewritableMap 中,则说明可能需要重写 470 // 任一规则不在 noRewritableMap 中,则说明可能需要重写
468 for (DataPermissionRule rule : rules) { 471 for (DataPermissionRule rule : rules) {
469 Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass()); 472 Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());
470 - if (!CollUtil.contains(mappedStatementIds, ms.getId())) { // 不存在,则说明可能要重写 473 + if (!CollUtil.contains(mappedStatementIds, ms.getId())) {
471 return false; 474 return false;
472 } 475 }
473 } 476 }
@@ -491,6 +494,14 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne @@ -491,6 +494,14 @@ public class DataPermissionInterceptor extends JsqlParserSupport implements Inne
491 } 494 }
492 } 495 }
493 496
  497 + /**
  498 + * 清空缓存
  499 + * 目前主要提供给单元测试
  500 + */
  501 + public void clear() {
  502 + noRewritableMappedStatements.clear();
  503 + }
  504 +
494 } 505 }
495 506
496 } 507 }
yudao-framework/yudao-spring-boot-starter-data-permission/src/test/java/cn/iocoder/yudao/framework/datapermission/core/interceptor/DataPermissionInterceptorTest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.datapermission.core.interceptor;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
  4 +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
  5 +import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRuleFactory;
  6 +import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
  7 +import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
  8 +import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
  9 +import net.sf.jsqlparser.expression.Alias;
  10 +import net.sf.jsqlparser.expression.Expression;
  11 +import net.sf.jsqlparser.expression.LongValue;
  12 +import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
  13 +import net.sf.jsqlparser.schema.Column;
  14 +import org.apache.ibatis.executor.Executor;
  15 +import org.apache.ibatis.executor.statement.StatementHandler;
  16 +import org.apache.ibatis.mapping.BoundSql;
  17 +import org.apache.ibatis.mapping.MappedStatement;
  18 +import org.junit.jupiter.api.BeforeEach;
  19 +import org.junit.jupiter.api.Test;
  20 +import org.mockito.InjectMocks;
  21 +import org.mockito.Mock;
  22 +import org.mockito.MockedStatic;
  23 +
  24 +import java.sql.Connection;
  25 +import java.util.*;
  26 +
  27 +import static java.util.Collections.singletonList;
  28 +import static org.junit.jupiter.api.Assertions.*;
  29 +import static org.mockito.Mockito.*;
  30 +
  31 +/**
  32 + * {@link DataPermissionInterceptor} 的单元测试
  33 + * 主要测试 {@link DataPermissionInterceptor#beforePrepare(StatementHandler, Connection, Integer)}
  34 + * 和 {@link DataPermissionInterceptor#beforeUpdate(Executor, MappedStatement, Object)}
  35 + * 以及在这个过程中,ContextHolder 和 MappedStatementCache
  36 + *
  37 + * @author 芋道源码
  38 + */
  39 +public class DataPermissionInterceptorTest extends BaseMockitoUnitTest {
  40 +
  41 + @InjectMocks
  42 + private DataPermissionInterceptor interceptor;
  43 +
  44 + @Mock
  45 + private DataPermissionRuleFactory ruleFactory;
  46 +
  47 + @BeforeEach
  48 + public void setUp() {
  49 + // 清理上下文
  50 + DataPermissionInterceptor.ContextHolder.clear();
  51 + // 清空缓存
  52 + interceptor.getMappedStatementCache().clear();
  53 + }
  54 +
  55 + @Test // 不存在规则,且不匹配
  56 + public void testBeforeQuery_withoutRule() {
  57 + try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
  58 + // 准备参数
  59 + MappedStatement mappedStatement = mock(MappedStatement.class);
  60 + BoundSql boundSql = mock(BoundSql.class);
  61 +
  62 + // 调用
  63 + interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
  64 + // 断言
  65 + pluginUtilsMock.verify(never(), () -> PluginUtils.mpBoundSql(boundSql));
  66 + }
  67 + }
  68 +
  69 + @Test // 存在规则,且不匹配
  70 + public void testBeforeQuery_withMatchRule() {
  71 + try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
  72 + // 准备参数
  73 + MappedStatement mappedStatement = mock(MappedStatement.class);
  74 + BoundSql boundSql = mock(BoundSql.class);
  75 + // mock 方法(数据权限)
  76 + when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))
  77 + .thenReturn(singletonList(new DeptDataPermissionRule()));
  78 + // mock 方法(MPBoundSql)
  79 + PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);
  80 + pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);
  81 + // mock 方法(SQL)
  82 + String sql = "select * from t_user where id = 1";
  83 + when(mpBs.sql()).thenReturn(sql);
  84 + // 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确
  85 +
  86 + // 调用
  87 + interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
  88 + // 断言
  89 + verify(mpBs, times(1)).sql(
  90 + eq("SELECT * FROM t_user WHERE id = 1 AND dept_id = 100"));
  91 + // 断言缓存
  92 + assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
  93 + }
  94 + }
  95 +
  96 + @Test // 存在规则,但不匹配
  97 + public void testBeforeQuery_withoutMatchRule() {
  98 + try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {
  99 + // 准备参数
  100 + MappedStatement mappedStatement = mock(MappedStatement.class);
  101 + BoundSql boundSql = mock(BoundSql.class);
  102 + // mock 方法(数据权限)
  103 + when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))
  104 + .thenReturn(singletonList(new DeptDataPermissionRule()));
  105 + // mock 方法(MPBoundSql)
  106 + PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);
  107 + pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);
  108 + // mock 方法(SQL)
  109 + String sql = "select * from t_role where id = 1";
  110 + when(mpBs.sql()).thenReturn(sql);
  111 + // 针对 ContextHolder 和 MappedStatementCache 暂时不 mock,主要想校验过程中,数据是否正确
  112 +
  113 + // 调用
  114 + interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);
  115 + // 断言
  116 + verify(mpBs, times(1)).sql(
  117 + eq("SELECT * FROM t_role WHERE id = 1"));
  118 + // 断言缓存
  119 + assertFalse(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());
  120 + }
  121 + }
  122 +
  123 + @Test
  124 + public void testAddNoRewritable() {
  125 + // 准备参数
  126 + MappedStatement ms = mock(MappedStatement.class);
  127 + List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());
  128 + // mock 方法
  129 + when(ms.getId()).thenReturn("selectById");
  130 +
  131 + // 调用
  132 + interceptor.getMappedStatementCache().addNoRewritable(ms, rules);
  133 + // 断言
  134 + Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements =
  135 + interceptor.getMappedStatementCache().getNoRewritableMappedStatements();
  136 + assertEquals(1, noRewritableMappedStatements.size());
  137 + assertEquals(SetUtils.asSet("selectById"), noRewritableMappedStatements.get(DeptDataPermissionRule.class));
  138 + }
  139 +
  140 + @Test
  141 + public void testNoRewritable() {
  142 + // 准备参数
  143 + MappedStatement ms = mock(MappedStatement.class);
  144 + // mock 方法
  145 + when(ms.getId()).thenReturn("selectById");
  146 + // mock 数据
  147 + List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());
  148 + interceptor.getMappedStatementCache().addNoRewritable(ms, rules);
  149 +
  150 + // 场景一,rules 为空
  151 + assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, null));
  152 + // 场景二,rules 非空,可重写
  153 + assertFalse(interceptor.getMappedStatementCache().noRewritable(ms, singletonList(new EmptyDataPermissionRule())));
  154 + // 场景三,rule 非空,不可重写
  155 + assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, rules));
  156 + }
  157 +
  158 + private static class DeptDataPermissionRule implements DataPermissionRule {
  159 +
  160 + private static final String COLUMN = "dept_id";
  161 +
  162 + @Override
  163 + public Set<String> getTableNames() {
  164 + return SetUtils.asSet("t_user");
  165 + }
  166 +
  167 + @Override
  168 + public Expression getExpression(String tableName, Alias tableAlias) {
  169 + Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);
  170 + LongValue value = new LongValue(100L);
  171 + return new EqualsTo(column, value);
  172 + }
  173 +
  174 + }
  175 +
  176 + private static class EmptyDataPermissionRule implements DataPermissionRule {
  177 +
  178 + @Override
  179 + public Set<String> getTableNames() {
  180 + return Collections.emptySet();
  181 + }
  182 +
  183 + @Override
  184 + public Expression getExpression(String tableName, Alias tableAlias) {
  185 + return null;
  186 + }
  187 +
  188 + }
  189 +
  190 +}
yudao-framework/yudao-spring-boot-starter-test/pom.xml
@@ -23,6 +23,10 @@ @@ -23,6 +23,10 @@
23 23
24 <!-- Test 测试相关 --> 24 <!-- Test 测试相关 -->
25 <dependency> 25 <dependency>
  26 + <groupId>org.mockito</groupId>
  27 + <artifactId>mockito-inline</artifactId>
  28 + </dependency>
  29 + <dependency>
26 <groupId>org.springframework.boot</groupId> 30 <groupId>org.springframework.boot</groupId>
27 <artifactId>spring-boot-starter-test</artifactId> 31 <artifactId>spring-boot-starter-test</artifactId>
28 </dependency> 32 </dependency>
更新日志.md
@@ -22,11 +22,12 @@ @@ -22,11 +22,12 @@
22 22
23 ### ⚠️ Warning 23 ### ⚠️ Warning
24 24
25 -这是一个多租户的预览版本,涉及的改动较大。 25 +这个版本新增了多租户与数据权限两个重量级的功能,建议花点时间进行了解与学习。
26 26
27 ### ⭐ New Features 27 ### ⭐ New Features
28 28
29 * 【新增】多租户,支持 Web、Security、Job、MQ、Async、DB、Redis 组件 29 * 【新增】多租户,支持 Web、Security、Job、MQ、Async、DB、Redis 组件
  30 +* 【新增】数据权限,内置基于部门过滤的规则
30 * 【新增】用户前台的昵称、头像的修改 31 * 【新增】用户前台的昵称、头像的修改
31 32
32 ### 🐞 Bug Fixes 33 ### 🐞 Bug Fixes
@@ -35,7 +36,7 @@ @@ -35,7 +36,7 @@
35 36
36 ### 🔨 Dependency Upgrades 37 ### 🔨 Dependency Upgrades
37 38
38 -暂无 39 +* 【引入】mockito-inline 3.6.28:Mockito 提供对 final、static 的支持
39 40
40 ### 📝 TODO 41 ### 📝 TODO
41 42