fix: 统一账号角色校验与默认首页
- 创建与导入账号时强制校验启用角色 - 按启用角色返回默认首页并过滤禁用角色
This commit is contained in:
@@ -2,28 +2,55 @@ package tech.easyflow.system.service.impl;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import cn.idev.excel.EasyExcel;
|
||||
import cn.idev.excel.ExcelWriter;
|
||||
import cn.idev.excel.FastExcel;
|
||||
import cn.idev.excel.context.AnalysisContext;
|
||||
import cn.idev.excel.metadata.data.ReadCellData;
|
||||
import cn.idev.excel.read.listener.ReadListener;
|
||||
import cn.idev.excel.write.metadata.WriteSheet;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import tech.easyflow.common.constant.enums.EnumAccountType;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.system.config.AccountSecurityProperties;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.entity.SysDept;
|
||||
import tech.easyflow.system.entity.vo.SysAccountImportErrorDetailVo;
|
||||
import tech.easyflow.system.entity.vo.SysAccountImportResultVo;
|
||||
import tech.easyflow.system.mapper.SysDeptMapper;
|
||||
import tech.easyflow.system.mapper.SysPositionMapper;
|
||||
import tech.easyflow.system.mapper.SysRoleMapper;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link SysAccountServiceImpl} 测试。
|
||||
@@ -97,6 +124,180 @@ public class SysAccountServiceImplTest {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证下载模板明确标记角色为必填字段。
|
||||
*
|
||||
* @throws Exception 注入测试配置失败
|
||||
*/
|
||||
@Test
|
||||
public void shouldMarkRoleAsRequiredInImportTemplate() throws Exception {
|
||||
SysAccountServiceImpl service = new SysAccountServiceImpl();
|
||||
AccountSecurityProperties properties = new AccountSecurityProperties();
|
||||
properties.setDefaultResetPassword("Template123!");
|
||||
setField(service, "accountSecurityProperties", properties);
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
|
||||
service.writeImportTemplate(outputStream);
|
||||
|
||||
List<String> headers = readFirstSheetHeaders(outputStream.toByteArray());
|
||||
assertTrue(headers.contains("角色名称*"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证缺少角色列时直接返回模板表头错误。
|
||||
*
|
||||
* @throws Exception 构造上传文件失败
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectImportTemplateWithoutRequiredRoleHeader() throws Exception {
|
||||
SysAccountServiceImpl service = new SysAccountServiceImpl();
|
||||
byte[] workbook = createImportWorkbook(
|
||||
List.of(
|
||||
List.of("部门名称*"),
|
||||
List.of("登录账号*"),
|
||||
List.of("昵称*")
|
||||
),
|
||||
List.of(List.of("研发部", "missing-role", "缺少角色"))
|
||||
);
|
||||
|
||||
BusinessException exception = assertThrows(
|
||||
BusinessException.class,
|
||||
() -> service.importAccounts(mockMultipartFile(workbook), new LoginAccount())
|
||||
);
|
||||
|
||||
assertTrue(exception.getMessage().contains("角色名称*"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证角色为空或仅包含分隔符时返回明确的行级必填错误。
|
||||
*
|
||||
* @throws Exception 注入测试依赖或构造上传文件失败
|
||||
*/
|
||||
@Test
|
||||
public void shouldRejectImportRowsWithoutEffectiveRoles() throws Exception {
|
||||
SysAccountServiceImpl service = spy(new SysAccountServiceImpl());
|
||||
SysDeptMapper deptMapper = mock(SysDeptMapper.class);
|
||||
SysRoleMapper roleMapper = mock(SysRoleMapper.class);
|
||||
SysPositionMapper positionMapper = mock(SysPositionMapper.class);
|
||||
PlatformTransactionManager transactionManager = mock(PlatformTransactionManager.class);
|
||||
TransactionStatus transactionStatus = mock(TransactionStatus.class);
|
||||
SysDept dept = new SysDept();
|
||||
dept.setId(BigInteger.ONE);
|
||||
dept.setDeptName("研发部");
|
||||
|
||||
when(deptMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of(dept));
|
||||
when(roleMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
|
||||
when(positionMapper.selectListByQuery(any(QueryWrapper.class))).thenReturn(List.of());
|
||||
when(transactionManager.getTransaction(any(TransactionDefinition.class))).thenReturn(transactionStatus);
|
||||
doReturn(0L).when(service).count(any(QueryWrapper.class));
|
||||
setField(service, "sysDeptMapper", deptMapper);
|
||||
setField(service, "sysRoleMapper", roleMapper);
|
||||
setField(service, "sysPositionMapper", positionMapper);
|
||||
setField(service, "transactionManager", transactionManager);
|
||||
|
||||
byte[] workbook = createImportWorkbook(
|
||||
List.of(
|
||||
List.of("部门名称*"),
|
||||
List.of("登录账号*"),
|
||||
List.of("昵称*"),
|
||||
List.of("角色名称")
|
||||
),
|
||||
List.of(
|
||||
List.of("研发部", "empty-role", "空角色", ""),
|
||||
List.of("研发部", "delimiter-role", "分隔符角色", ", , ")
|
||||
)
|
||||
);
|
||||
LoginAccount loginAccount = new LoginAccount();
|
||||
loginAccount.setId(BigInteger.TEN);
|
||||
SysAccountImportResultVo result = service.importAccounts(
|
||||
mockMultipartFile(workbook),
|
||||
loginAccount
|
||||
);
|
||||
|
||||
assertEquals(2, result.getTotalCount());
|
||||
assertEquals(0, result.getSuccessCount());
|
||||
assertEquals(2, result.getErrorCount());
|
||||
result.getErrorRows().forEach(errorRow -> {
|
||||
List<SysAccountImportErrorDetailVo> details = errorRow.getDetails();
|
||||
assertTrue(details.stream().anyMatch(detail ->
|
||||
"角色名称".equals(detail.getFieldName())
|
||||
&& "角色名称不能为空".equals(detail.getReason())
|
||||
));
|
||||
});
|
||||
verify(service, never()).save(any(SysAccount.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建账号导入测试工作簿。
|
||||
*
|
||||
* @param headers 表头
|
||||
* @param rows 数据行
|
||||
* @return 工作簿字节
|
||||
*/
|
||||
private byte[] createImportWorkbook(List<List<String>> headers, List<List<String>> rows) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ExcelWriter excelWriter = EasyExcel.write(outputStream).build();
|
||||
try {
|
||||
WriteSheet sheet = EasyExcel.writerSheet("模板")
|
||||
.head(headers)
|
||||
.build();
|
||||
excelWriter.write(rows, sheet);
|
||||
} finally {
|
||||
excelWriter.finish();
|
||||
}
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取工作簿首个工作表的表头。
|
||||
*
|
||||
* @param workbook 工作簿字节
|
||||
* @return 表头文本
|
||||
*/
|
||||
private List<String> readFirstSheetHeaders(byte[] workbook) {
|
||||
List<String> headers = new ArrayList<>();
|
||||
ReadListener<LinkedHashMap<Integer, Object>> listener =
|
||||
new ReadListener<LinkedHashMap<Integer, Object>>() {
|
||||
@Override
|
||||
public void invoke(LinkedHashMap<Integer, Object> data, AnalysisContext context) {
|
||||
// 模板没有数据行,仅检查表头。
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
|
||||
headMap.values().stream()
|
||||
.map(ReadCellData::getStringValue)
|
||||
.filter(value -> value != null && !value.isBlank())
|
||||
.forEach(headers::add);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doAfterAllAnalysed(AnalysisContext context) {
|
||||
// 无需汇总数据行。
|
||||
}
|
||||
};
|
||||
FastExcel.read(new ByteArrayInputStream(workbook), listener)
|
||||
.sheet()
|
||||
.doRead();
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造账号导入上传文件。
|
||||
*
|
||||
* @param workbook 工作簿字节
|
||||
* @return 上传文件
|
||||
* @throws Exception 模拟输入流失败
|
||||
*/
|
||||
private MultipartFile mockMultipartFile(byte[] workbook) throws Exception {
|
||||
MultipartFile file = mock(MultipartFile.class);
|
||||
when(file.isEmpty()).thenReturn(workbook.length == 0);
|
||||
when(file.getSize()).thenReturn((long) workbook.length);
|
||||
when(file.getOriginalFilename()).thenReturn("users.xlsx");
|
||||
when(file.getInputStream()).thenAnswer(invocation -> new ByteArrayInputStream(workbook));
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置测试对象的私有字段。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package tech.easyflow.system.service.impl;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.junit.Test;
|
||||
import tech.easyflow.common.constant.enums.EnumDataStatus;
|
||||
import tech.easyflow.system.entity.SysAccountRole;
|
||||
import tech.easyflow.system.entity.SysRole;
|
||||
import tech.easyflow.system.service.SysAccountRoleService;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link SysRoleServiceImpl} 角色状态过滤测试。
|
||||
*/
|
||||
public class SysRoleServiceImplTest {
|
||||
|
||||
/**
|
||||
* 验证账号角色查询只返回启用角色。
|
||||
*
|
||||
* @throws Exception 注入测试依赖失败
|
||||
*/
|
||||
@Test
|
||||
public void getRolesByAccountIdShouldExcludeUnavailableRoles() throws Exception {
|
||||
BigInteger accountId = BigInteger.valueOf(10);
|
||||
BigInteger availableRoleId = BigInteger.valueOf(20);
|
||||
BigInteger unavailableRoleId = BigInteger.valueOf(30);
|
||||
SysAccountRoleService accountRoleService = mock(SysAccountRoleService.class);
|
||||
SysAccountRole availableRelation = accountRole(accountId, availableRoleId);
|
||||
SysAccountRole unavailableRelation = accountRole(accountId, unavailableRoleId);
|
||||
when(accountRoleService.list(any(QueryWrapper.class)))
|
||||
.thenReturn(List.of(availableRelation, unavailableRelation));
|
||||
|
||||
SysRole availableRole = role(availableRoleId, EnumDataStatus.AVAILABLE.getCode());
|
||||
SysRole unavailableRole = role(unavailableRoleId, EnumDataStatus.UNAVAILABLE.getCode());
|
||||
TestSysRoleService service = new TestSysRoleService(List.of(availableRole, unavailableRole));
|
||||
setField(service, "sysAccountRoleService", accountRoleService);
|
||||
|
||||
List<SysRole> roles = service.getRolesByAccountId(accountId);
|
||||
|
||||
assertEquals(List.of(availableRole), roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造账号角色关系。
|
||||
*
|
||||
* @param accountId 账号 ID
|
||||
* @param roleId 角色 ID
|
||||
* @return 账号角色关系
|
||||
*/
|
||||
private SysAccountRole accountRole(BigInteger accountId, BigInteger roleId) {
|
||||
SysAccountRole relation = new SysAccountRole();
|
||||
relation.setAccountId(accountId);
|
||||
relation.setRoleId(roleId);
|
||||
return relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造角色。
|
||||
*
|
||||
* @param roleId 角色 ID
|
||||
* @param status 角色状态
|
||||
* @return 角色
|
||||
*/
|
||||
private SysRole role(BigInteger roleId, Integer status) {
|
||||
SysRole role = new SysRole();
|
||||
role.setId(roleId);
|
||||
role.setStatus(status);
|
||||
return role;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注入私有字段。
|
||||
*
|
||||
* @param target 目标对象
|
||||
* @param fieldName 字段名称
|
||||
* @param value 字段值
|
||||
* @throws Exception 字段不存在或无法写入
|
||||
*/
|
||||
private void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Field field = SysRoleServiceImpl.class.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
field.set(target, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提供固定角色集合的测试服务。
|
||||
*/
|
||||
private static final class TestSysRoleService extends SysRoleServiceImpl {
|
||||
|
||||
private final List<SysRole> roles;
|
||||
|
||||
/**
|
||||
* 创建测试服务。
|
||||
*
|
||||
* @param roles 固定角色集合
|
||||
*/
|
||||
private TestSysRoleService(List<SysRole> roles) {
|
||||
this.roles = roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public List<SysRole> listByIds(Collection<? extends Serializable> ids) {
|
||||
return roles;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user