test
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package com.ruoyi.info.collection.controller;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||
import com.ruoyi.info.collection.domain.vo.BaseStaffImportSubmitResultVO;
|
||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiBaseStaffControllerTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiBaseStaffController controller;
|
||||
|
||||
@Mock
|
||||
private ICcdiBaseStaffService baseStaffService;
|
||||
|
||||
@Mock
|
||||
private ICcdiBaseStaffImportService importAsyncService;
|
||||
|
||||
@Mock
|
||||
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
|
||||
|
||||
@Test
|
||||
void importTemplate_shouldDownloadDualSheetTemplate() {
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
controller.importTemplate(null);
|
||||
|
||||
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
null,
|
||||
CcdiBaseStaffExcel.class,
|
||||
"员工信息",
|
||||
CcdiBaseStaffAssetInfoExcel.class,
|
||||
"员工资产信息",
|
||||
"员工信息维护导入模板"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importData_shouldWarnWhenBothSheetsAreEmpty() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"base-staff-empty.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"empty".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
|
||||
.thenReturn(List.of());
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
|
||||
.thenReturn(List.of());
|
||||
|
||||
AjaxResult result = controller.importData(file);
|
||||
|
||||
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
|
||||
verifyNoInteractions(baseStaffService);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importData_shouldSubmitOnlyStaffTaskWhenOnlyStaffSheetHasRows() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"base-staff.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"staff".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
CcdiBaseStaffExcel staffExcel = new CcdiBaseStaffExcel();
|
||||
staffExcel.setStaffId(1001L);
|
||||
BaseStaffImportSubmitResultVO submitResult = new BaseStaffImportSubmitResultVO();
|
||||
submitResult.setStaffTaskId("staff-task-1");
|
||||
when(baseStaffService.importBaseStaffWithAssets(List.of(staffExcel), List.of())).thenReturn(submitResult);
|
||||
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
|
||||
.thenReturn(List.of(staffExcel));
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
|
||||
.thenReturn(List.of());
|
||||
|
||||
AjaxResult result = controller.importData(file);
|
||||
|
||||
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG));
|
||||
Object data = result.get(AjaxResult.DATA_TAG);
|
||||
assertEquals("staff-task-1", data.getClass().getMethod("getStaffTaskId").invoke(data));
|
||||
assertNull(data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importData_shouldSubmitTwoTasksWhenBothSheetsHaveRows() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"base-staff-both.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"both".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
CcdiBaseStaffExcel staffExcel = new CcdiBaseStaffExcel();
|
||||
staffExcel.setStaffId(1002L);
|
||||
CcdiBaseStaffAssetInfoExcel assetExcel = new CcdiBaseStaffAssetInfoExcel();
|
||||
assetExcel.setPersonId("320101199001010011");
|
||||
BaseStaffImportSubmitResultVO submitResult = new BaseStaffImportSubmitResultVO();
|
||||
submitResult.setStaffTaskId("staff-task-2");
|
||||
submitResult.setAssetTaskId("asset-task-2");
|
||||
when(baseStaffService.importBaseStaffWithAssets(List.of(staffExcel), List.of(assetExcel))).thenReturn(submitResult);
|
||||
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffExcel.class), eq("员工信息")))
|
||||
.thenReturn(List.of(staffExcel));
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiBaseStaffAssetInfoExcel.class), eq("员工资产信息")))
|
||||
.thenReturn(List.of(assetExcel));
|
||||
|
||||
AjaxResult result = controller.importData(file);
|
||||
|
||||
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||
Object data = result.get(AjaxResult.DATA_TAG);
|
||||
assertEquals("staff-task-2", data.getClass().getMethod("getStaffTaskId").invoke(data));
|
||||
assertEquals("asset-task-2", data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.info.collection.controller;
|
||||
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.info.collection.domain.vo.EnumOptionVO;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
class CcdiEnumControllerTest {
|
||||
|
||||
private final CcdiEnumController controller = new CcdiEnumController();
|
||||
|
||||
@Test
|
||||
void getEnterpriseRiskLevelOptions_shouldReturnConfiguredOptions() {
|
||||
AjaxResult result = controller.getEnterpriseRiskLevelOptions();
|
||||
List<?> data = (List<?>) result.get("data");
|
||||
|
||||
assertEquals(3, data.size());
|
||||
EnumOptionVO first = (EnumOptionVO) data.get(0);
|
||||
assertEquals("1", first.getValue());
|
||||
assertEquals("高风险", first.getLabel());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getEnterpriseSourceOptions_shouldReturnConfiguredOptions() {
|
||||
AjaxResult result = controller.getEnterpriseSourceOptions();
|
||||
List<?> data = (List<?>) result.get("data");
|
||||
|
||||
assertFalse(data.isEmpty());
|
||||
EnumOptionVO first = (EnumOptionVO) data.get(0);
|
||||
assertEquals("GENERAL", first.getValue());
|
||||
assertEquals("一般企业", first.getLabel());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package com.ruoyi.info.collection.controller;
|
||||
|
||||
import com.ruoyi.common.constant.HttpStatus;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
|
||||
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
|
||||
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
|
||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiStaffFmyRelationControllerTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiStaffFmyRelationController controller;
|
||||
|
||||
@Mock
|
||||
private ICcdiStaffFmyRelationService relationService;
|
||||
|
||||
@Mock
|
||||
private ICcdiStaffFmyRelationImportService relationImportService;
|
||||
|
||||
@Mock
|
||||
private ICcdiAssetInfoImportService assetInfoImportService;
|
||||
|
||||
@Test
|
||||
void importTemplate_shouldDownloadDualSheetTemplate() {
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
controller.importTemplate(null);
|
||||
|
||||
mocked.verify(() -> EasyExcelUtil.importTemplateWithDictDropdown(
|
||||
null,
|
||||
CcdiStaffFmyRelationExcel.class,
|
||||
"员工亲属关系信息",
|
||||
CcdiAssetInfoExcel.class,
|
||||
"亲属资产信息",
|
||||
"员工亲属关系维护导入模板"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importData_shouldErrorWhenBothSheetsAreEmpty() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"staff-family-empty.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"empty".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
|
||||
.thenReturn(List.of());
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
|
||||
.thenReturn(List.of());
|
||||
|
||||
AjaxResult result = controller.importData(file);
|
||||
|
||||
assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("至少需要一条数据", result.get(AjaxResult.MSG_TAG));
|
||||
verifyNoInteractions(relationService, assetInfoImportService);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importData_shouldSubmitOnlyRelationTaskWhenOnlyRelationSheetHasRows() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"staff-family-relation.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"relation".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
CcdiStaffFmyRelationExcel relationExcel = new CcdiStaffFmyRelationExcel();
|
||||
relationExcel.setPersonId("320101199001010011");
|
||||
StaffFmyRelationImportSubmitResultVO submitResult = new StaffFmyRelationImportSubmitResultVO();
|
||||
submitResult.setRelationTaskId("relation-task-1");
|
||||
when(relationService.importRelationWithAssets(List.of(relationExcel), List.of())).thenReturn(submitResult);
|
||||
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
|
||||
.thenReturn(List.of(relationExcel));
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
|
||||
.thenReturn(List.of());
|
||||
|
||||
AjaxResult result = controller.importData(file);
|
||||
|
||||
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||
assertEquals("导入任务已提交,正在后台处理", result.get(AjaxResult.MSG_TAG));
|
||||
Object data = result.get(AjaxResult.DATA_TAG);
|
||||
assertEquals("relation-task-1", data.getClass().getMethod("getRelationTaskId").invoke(data));
|
||||
assertNull(data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void importData_shouldSubmitTwoTasksWhenBothSheetsHaveRows() throws Exception {
|
||||
MockMultipartFile file = new MockMultipartFile(
|
||||
"file",
|
||||
"staff-family-both.xlsx",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"both".getBytes(StandardCharsets.UTF_8)
|
||||
);
|
||||
CcdiStaffFmyRelationExcel relationExcel = new CcdiStaffFmyRelationExcel();
|
||||
relationExcel.setPersonId("320101199001010012");
|
||||
CcdiAssetInfoExcel assetExcel = new CcdiAssetInfoExcel();
|
||||
assetExcel.setPersonId("320101199001010099");
|
||||
StaffFmyRelationImportSubmitResultVO submitResult = new StaffFmyRelationImportSubmitResultVO();
|
||||
submitResult.setRelationTaskId("relation-task-2");
|
||||
submitResult.setAssetTaskId("asset-task-2");
|
||||
when(relationService.importRelationWithAssets(List.of(relationExcel), List.of(assetExcel))).thenReturn(submitResult);
|
||||
|
||||
try (MockedStatic<EasyExcelUtil> mocked = mockStatic(EasyExcelUtil.class)) {
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiStaffFmyRelationExcel.class), eq("员工亲属关系信息")))
|
||||
.thenReturn(List.of(relationExcel));
|
||||
mocked.when(() -> EasyExcelUtil.importExcel(any(InputStream.class), eq(CcdiAssetInfoExcel.class), eq("亲属资产信息")))
|
||||
.thenReturn(List.of(assetExcel));
|
||||
|
||||
AjaxResult result = controller.importData(file);
|
||||
|
||||
assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
|
||||
Object data = result.get(AjaxResult.DATA_TAG);
|
||||
assertEquals("relation-task-2", data.getClass().getMethod("getRelationTaskId").invoke(data));
|
||||
assertEquals("asset-task-2", data.getClass().getMethod("getAssetTaskId").invoke(data));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getImportFailures_shouldReturnPagedRowsWithSheetAndRowInfo() {
|
||||
StaffFmyRelationImportFailureVO failure1 = new StaffFmyRelationImportFailureVO();
|
||||
failure1.setSheetName("员工亲属关系信息");
|
||||
failure1.setRowNum(2);
|
||||
failure1.setPersonId("A1");
|
||||
|
||||
StaffFmyRelationImportFailureVO failure2 = new StaffFmyRelationImportFailureVO();
|
||||
failure2.setSheetName("员工亲属关系信息");
|
||||
failure2.setRowNum(3);
|
||||
failure2.setPersonId("A2");
|
||||
|
||||
when(relationImportService.getImportFailures("task-1")).thenReturn(List.of(failure1, failure2));
|
||||
|
||||
TableDataInfo result = controller.getImportFailures("task-1", 2, 1);
|
||||
|
||||
assertEquals(2, result.getTotal());
|
||||
assertEquals(1, result.getRows().size());
|
||||
StaffFmyRelationImportFailureVO row = (StaffFmyRelationImportFailureVO) result.getRows().get(0);
|
||||
assertEquals("员工亲属关系信息", row.getSheetName());
|
||||
assertEquals(3, row.getRowNum());
|
||||
assertEquals("A2", row.getPersonId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.info.collection.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiEnterpriseBaseInfoMapperTest {
|
||||
|
||||
@Test
|
||||
void mapperXml_shouldContainPageQueryAndImportColumns() throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader()
|
||||
.getResourceAsStream("mapper/info/collection/CcdiEnterpriseBaseInfoMapper.xml")) {
|
||||
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(xml.contains("selectEnterpriseBaseInfoPage"), xml);
|
||||
assertTrue(xml.contains("risk_level"), xml);
|
||||
assertTrue(xml.contains("ent_source"), xml);
|
||||
assertTrue(xml.contains("data_source"), xml);
|
||||
assertTrue(xml.contains("ORDER BY create_time DESC"), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.info.collection.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiIntermediaryMapperTest {
|
||||
|
||||
@Test
|
||||
void mapperXml_shouldContainThreeRecordTypesAndRelatedKeywordQuery() throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader()
|
||||
.getResourceAsStream("mapper/info/collection/CcdiIntermediaryMapper.xml")) {
|
||||
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(xml.contains("INTERMEDIARY"), xml);
|
||||
assertTrue(xml.contains("RELATIVE"), xml);
|
||||
assertTrue(xml.contains("ENTERPRISE_RELATION"), xml);
|
||||
assertTrue(xml.contains("relatedIntermediaryKeyword"), xml);
|
||||
assertTrue(xml.contains("related_intermediary_name"), xml);
|
||||
assertTrue(xml.contains("relation_text"), xml);
|
||||
assertTrue(xml.contains("CAST('实体'"), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||
import com.ruoyi.info.collection.domain.vo.ImportFailureVO;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiBaseStaffImportServiceImpl;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.redis.core.HashOperations;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiBaseStaffDualImportServiceTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiBaseStaffImportServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper baseStaffMapper;
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Mock
|
||||
private HashOperations<String, Object, Object> hashOperations;
|
||||
|
||||
@Mock
|
||||
private ValueOperations<String, Object> valueOperations;
|
||||
|
||||
@Mock
|
||||
private SysDeptMapper deptMapper;
|
||||
|
||||
@Test
|
||||
void importBaseStaffAsync_shouldTreatExistingEmployeeAsFailureInsteadOfUpdate() {
|
||||
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
|
||||
excel.setStaffId(1001L);
|
||||
excel.setName("张三");
|
||||
excel.setDeptId(10L);
|
||||
excel.setIdCard("11010519491231002X");
|
||||
excel.setPhone("13812345678");
|
||||
excel.setStatus("0");
|
||||
excel.setPartyMember(1);
|
||||
|
||||
CcdiBaseStaff existing = new CcdiBaseStaff();
|
||||
existing.setStaffId(1001L);
|
||||
existing.setIdCard("11010519491231002X");
|
||||
|
||||
when(baseStaffMapper.selectBatchIds(List.of(1001L))).thenReturn(List.of(existing));
|
||||
when(baseStaffMapper.selectList(any())).thenReturn(List.of(existing));
|
||||
lenient().when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
|
||||
service.importBaseStaffAsync(List.of(excel), "task-existing");
|
||||
|
||||
verify(baseStaffMapper, never()).insertBatch(any());
|
||||
verify(baseStaffMapper, never()).insertOrUpdateBatch(any());
|
||||
|
||||
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(valueOperations).set(eq("import:baseStaff:task-existing:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||
ImportFailureVO failure = (ImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||
assertEquals("员工信息", failure.getSheetName());
|
||||
assertEquals(2, failure.getRowNum());
|
||||
assertEquals(1001L, failure.getStaffId());
|
||||
assertEquals("该员工ID已存在", failure.getErrorMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateStaffData_shouldRejectExistingIdCardWhenStaffIdDoesNotExist() {
|
||||
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
|
||||
|
||||
RuntimeException exception = org.junit.jupiter.api.Assertions.assertThrows(
|
||||
RuntimeException.class,
|
||||
() -> service.validateStaffData(buildExcelDto(), Set.of(), Set.of("11010519491231002X"))
|
||||
);
|
||||
|
||||
assertEquals("该身份证号已存在", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importBaseStaffAsync_shouldSaveFailureWhenDeptIsInvalid() {
|
||||
CcdiBaseStaffExcel validExcel = buildExcel(1001L, 10L, "11010519491231002X");
|
||||
CcdiBaseStaffExcel invalidExcel = buildExcel(1002L, 99L, "320101199001010014");
|
||||
|
||||
when(baseStaffMapper.selectBatchIds(List.of(1001L, 1002L))).thenReturn(List.of());
|
||||
when(baseStaffMapper.selectList(any())).thenReturn(List.of());
|
||||
when(deptMapper.selectDeptById(10L)).thenReturn(buildDept(10L, "0", "0"));
|
||||
when(deptMapper.selectDeptById(99L)).thenReturn(null);
|
||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||
|
||||
service.importBaseStaffAsync(List.of(validExcel, invalidExcel), "task-invalid-dept");
|
||||
|
||||
verify(baseStaffMapper).insertBatch(any());
|
||||
|
||||
ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
|
||||
verify(valueOperations).set(eq("import:baseStaff:task-invalid-dept:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
|
||||
ImportFailureVO failure = (ImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
|
||||
assertEquals("员工信息", failure.getSheetName());
|
||||
assertEquals(3, failure.getRowNum());
|
||||
assertEquals(1002L, failure.getStaffId());
|
||||
assertEquals("所属部门ID[99]不存在或已停用/删除,请检查机构号", failure.getErrorMessage());
|
||||
|
||||
ArgumentCaptor<Map<String, Object>> statusCaptor = ArgumentCaptor.forClass(Map.class);
|
||||
verify(hashOperations).putAll(eq("import:baseStaff:task-invalid-dept"), statusCaptor.capture());
|
||||
assertEquals("PARTIAL_SUCCESS", statusCaptor.getValue().get("status"));
|
||||
assertEquals(1, statusCaptor.getValue().get("successCount"));
|
||||
assertEquals(1, statusCaptor.getValue().get("failureCount"));
|
||||
}
|
||||
|
||||
private com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO buildExcelDto() {
|
||||
com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO dto = new com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO();
|
||||
dto.setName("李四");
|
||||
dto.setStaffId(2001L);
|
||||
dto.setDeptId(10L);
|
||||
dto.setIdCard("11010519491231002X");
|
||||
dto.setPhone("13812345678");
|
||||
dto.setStatus("0");
|
||||
dto.setPartyMember(1);
|
||||
return dto;
|
||||
}
|
||||
|
||||
private CcdiBaseStaffExcel buildExcel(Long staffId, Long deptId, String idCard) {
|
||||
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
|
||||
excel.setStaffId(staffId);
|
||||
excel.setName("张三");
|
||||
excel.setDeptId(deptId);
|
||||
excel.setIdCard(idCard);
|
||||
excel.setPhone("13812345678");
|
||||
excel.setStatus("0");
|
||||
excel.setPartyMember(1);
|
||||
return excel;
|
||||
}
|
||||
|
||||
private SysDept buildDept(Long deptId, String status, String delFlag) {
|
||||
SysDept dept = new SysDept();
|
||||
dept.setDeptId(deptId);
|
||||
dept.setDeptName("测试部门");
|
||||
dept.setStatus(status);
|
||||
dept.setDelFlag(delFlag);
|
||||
return dept;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiIntermediaryRelativeAddDTO;
|
||||
import com.ruoyi.info.collection.domain.dto.CcdiIntermediaryPersonAddDTO;
|
||||
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryMapper;
|
||||
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiIntermediaryServiceImplTest {
|
||||
|
||||
@InjectMocks
|
||||
private CcdiIntermediaryServiceImpl service;
|
||||
|
||||
@Mock
|
||||
private CcdiBizIntermediaryMapper bizIntermediaryMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiIntermediaryMapper intermediaryMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiIntermediaryEnterpriseRelationMapper enterpriseRelationMapper;
|
||||
|
||||
@Mock
|
||||
private ICcdiIntermediaryPersonImportService personImportService;
|
||||
|
||||
@Mock
|
||||
private ICcdiIntermediaryEntityImportService entityImportService;
|
||||
|
||||
@Mock
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Test
|
||||
void insertIntermediaryPerson_shouldForceBenrenAndClearRelatedNumId() {
|
||||
CcdiIntermediaryPersonAddDTO addDTO = new CcdiIntermediaryPersonAddDTO();
|
||||
addDTO.setName("测试中介");
|
||||
addDTO.setPersonId("320101199001010011");
|
||||
addDTO.setPersonSubType("配偶");
|
||||
addDTO.setRelatedNumId("parent-id");
|
||||
|
||||
when(bizIntermediaryMapper.selectCount(any())).thenReturn(0L);
|
||||
when(bizIntermediaryMapper.insert(any(CcdiBizIntermediary.class))).thenReturn(1);
|
||||
|
||||
int result = service.insertIntermediaryPerson(addDTO);
|
||||
|
||||
assertEquals(1, result);
|
||||
ArgumentCaptor<CcdiBizIntermediary> captor = ArgumentCaptor.forClass(CcdiBizIntermediary.class);
|
||||
verify(bizIntermediaryMapper).insert(captor.capture());
|
||||
assertEquals("本人", captor.getValue().getPersonSubType());
|
||||
assertNull(captor.getValue().getRelatedNumId());
|
||||
assertEquals("MANUAL", captor.getValue().getDataSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
void insertIntermediaryRelative_shouldRejectBenrenSubType() {
|
||||
CcdiBizIntermediary owner = new CcdiBizIntermediary();
|
||||
owner.setBizId("biz-1");
|
||||
owner.setPersonSubType("本人");
|
||||
|
||||
CcdiIntermediaryRelativeAddDTO addDTO = new CcdiIntermediaryRelativeAddDTO();
|
||||
addDTO.setName("测试亲属");
|
||||
addDTO.setPersonId("320101199001010022");
|
||||
addDTO.setPersonSubType("本人");
|
||||
|
||||
when(bizIntermediaryMapper.selectById("biz-1")).thenReturn(owner);
|
||||
|
||||
RuntimeException exception = assertThrows(RuntimeException.class,
|
||||
() -> service.insertIntermediaryRelative("biz-1", addDTO));
|
||||
|
||||
assertEquals("亲属关系不能为本人", exception.getMessage());
|
||||
verify(bizIntermediaryMapper, never()).insert(any(CcdiBizIntermediary.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteIntermediaryByIds_shouldDeleteRelativesAndEnterpriseRelationsWhenRemovingOwner() {
|
||||
CcdiBizIntermediary owner = new CcdiBizIntermediary();
|
||||
owner.setBizId("biz-1");
|
||||
owner.setPersonSubType("本人");
|
||||
|
||||
when(bizIntermediaryMapper.selectById("biz-1")).thenReturn(owner);
|
||||
when(bizIntermediaryMapper.delete(any())).thenReturn(2);
|
||||
when(enterpriseRelationMapper.delete(any())).thenReturn(1);
|
||||
when(bizIntermediaryMapper.deleteById("biz-1")).thenReturn(1);
|
||||
|
||||
int result = service.deleteIntermediaryByIds(new String[]{"biz-1"});
|
||||
|
||||
assertEquals(1, result);
|
||||
verify(bizIntermediaryMapper).delete(any());
|
||||
verify(enterpriseRelationMapper).delete(any());
|
||||
verify(bizIntermediaryMapper).deleteById("biz-1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package com.ruoyi.info.collection.service;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiPurchaseTransactionFeatureContractTest {
|
||||
|
||||
@Test
|
||||
void shouldExposeSupplierListContractsAcrossPurchaseTransactionModels() throws Exception {
|
||||
assertHasField(
|
||||
"com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionAddDTO",
|
||||
"supplierList"
|
||||
);
|
||||
assertHasField(
|
||||
"com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionEditDTO",
|
||||
"supplierList"
|
||||
);
|
||||
assertHasField(
|
||||
"com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO",
|
||||
"supplierList"
|
||||
);
|
||||
assertHasField(
|
||||
"com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO",
|
||||
"supplierCount"
|
||||
);
|
||||
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.CcdiPurchaseTransactionSupplier"));
|
||||
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.dto.CcdiPurchaseTransactionSupplierDTO"));
|
||||
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO"));
|
||||
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDefineSupplierSubTableAndBiddingMigrationScripts() throws Exception {
|
||||
String initSql = Files.readString(repoPath("sql/ccdi_purchase_transaction.sql"));
|
||||
assertTrue(initSql.contains("CREATE TABLE `ccdi_purchase_transaction_supplier`"));
|
||||
assertTrue(initSql.contains("`is_bid_winner`"));
|
||||
assertTrue(initSql.contains("`sort_order`"));
|
||||
assertTrue(initSql.contains("utf8mb4_general_ci"));
|
||||
|
||||
String menuSql = Files.readString(repoPath("sql/ccdi_purchase_transaction_menu.sql"));
|
||||
assertTrue(menuSql.contains("招投标信息维护"));
|
||||
|
||||
Path migrationPath = repoPath("sql/migration/2026-04-22-bidding-info-maintenance-supplier-detail.sql");
|
||||
assertTrue(Files.exists(migrationPath), "应提供招投标供应商明细迁移脚本");
|
||||
|
||||
String migrationSql = Files.readString(migrationPath);
|
||||
assertTrue(migrationSql.contains("ccdi_purchase_transaction_supplier"));
|
||||
assertTrue(migrationSql.contains("INSERT INTO ccdi_purchase_transaction_supplier"));
|
||||
assertTrue(migrationSql.contains("UPDATE sys_menu"));
|
||||
assertTrue(migrationSql.contains("招投标信息维护"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseTwoSheetTemplateForBiddingImport() throws Exception {
|
||||
assertNotNull(Class.forName("com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel"));
|
||||
|
||||
String controller = Files.readString(
|
||||
Path.of("src/main/java/com/ruoyi/info/collection/controller/CcdiPurchaseTransactionController.java")
|
||||
);
|
||||
assertTrue(controller.contains("招投标主信息"));
|
||||
assertTrue(controller.contains("供应商明细"));
|
||||
}
|
||||
|
||||
private void assertHasField(String className, String fieldName) throws Exception {
|
||||
Class<?> clazz = Class.forName(className);
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
assertNotNull(field);
|
||||
}
|
||||
|
||||
private Path repoPath(String relativePath) {
|
||||
return Path.of("..", relativePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
package com.ruoyi.info.collection.utils;
|
||||
|
||||
import com.alibaba.excel.annotation.ExcelProperty;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||
import org.apache.poi.ss.usermodel.CellType;
|
||||
import org.apache.poi.ss.usermodel.DataValidation;
|
||||
import org.apache.poi.ss.usermodel.DataValidationConstraint;
|
||||
import org.apache.poi.ss.usermodel.DataValidationHelper;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class EasyExcelUtilImportDropdownValidationTest {
|
||||
|
||||
@Test
|
||||
void importExcel_shouldPassWhenAllDictDropdownColumnsKeepListValidation() throws Exception {
|
||||
byte[] bytes = baseStaffWorkbook(true, true, true, 2);
|
||||
|
||||
List<CcdiBaseStaffExcel> rows = EasyExcelUtil.importExcel(
|
||||
new ByteArrayInputStream(bytes),
|
||||
CcdiBaseStaffExcel.class,
|
||||
"员工信息"
|
||||
);
|
||||
|
||||
assertEquals(2, rows.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldFailWhenPartyMemberDropdownIsMissing() throws Exception {
|
||||
byte[] bytes = baseStaffWorkbook(false, true, true, 2);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||
);
|
||||
|
||||
assertTrue(exception.getMessage().contains("是否党员 列缺少下拉框"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldFailWhenStatusDropdownIsMissing() throws Exception {
|
||||
byte[] bytes = baseStaffWorkbook(true, false, true, 2);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||
);
|
||||
|
||||
assertEquals("员工信息 Sheet 的 状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldReportAllMissingDropdownColumnsInSameSheet() throws Exception {
|
||||
byte[] bytes = baseStaffWorkbook(false, false, true, 2);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||
);
|
||||
|
||||
assertEquals("员工信息 Sheet 的 是否党员、状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldFailWhenValidationIsNotListType() throws Exception {
|
||||
byte[] bytes = baseStaffWorkbook(true, true, false, 2);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||
);
|
||||
|
||||
assertTrue(exception.getMessage().contains("状态 列缺少下拉框"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldFailWhenListValidationDoesNotCoverEveryActualDataRow() throws Exception {
|
||||
byte[] bytes = baseStaffWorkbook(true, true, true, 1);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffExcel.class, "员工信息")
|
||||
);
|
||||
|
||||
assertTrue(exception.getMessage().contains("状态 列缺少下拉框"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldFailWhenSecondSheetDropdownIsMissing() throws Exception {
|
||||
byte[] bytes = baseStaffDualSheetWorkbookWithMissingAssetStatusDropdown();
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class, () ->
|
||||
EasyExcelUtil.importExcel(new ByteArrayInputStream(bytes), CcdiBaseStaffAssetInfoExcel.class, "员工资产信息")
|
||||
);
|
||||
|
||||
assertEquals("员工资产信息 Sheet 的 资产状态 列缺少下拉框,请下载最新导入模板填写后重新导入", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void importExcel_shouldSkipDropdownStructureValidationWhenClassHasNoDictDropdownFields() throws Exception {
|
||||
byte[] bytes = plainWorkbookWithoutDropdown();
|
||||
|
||||
List<PlainExcel> rows = EasyExcelUtil.importExcel(
|
||||
new ByteArrayInputStream(bytes),
|
||||
PlainExcel.class,
|
||||
"普通信息"
|
||||
);
|
||||
|
||||
assertEquals(1, rows.size());
|
||||
}
|
||||
|
||||
private byte[] baseStaffWorkbook(boolean partyDropdown, boolean statusDropdown, boolean statusAsList,
|
||||
int statusLastRow) throws Exception {
|
||||
try (Workbook workbook = new XSSFWorkbook();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = workbook.createSheet("员工信息");
|
||||
Row header = sheet.createRow(0);
|
||||
String[] headers = {"姓名", "员工ID", "所属部门ID", "身份证号", "电话", "年收入(元/年)",
|
||||
"入职时间", "是否党员", "状态"};
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
header.createCell(i).setCellValue(headers[i]);
|
||||
}
|
||||
createBaseStaffRow(sheet, 1, "张三", 9020001L, "33010619850202101X", "0", "1");
|
||||
createBaseStaffRow(sheet, 2, "李四", 9020002L, "330106198603031022", "1", "1");
|
||||
|
||||
if (partyDropdown) {
|
||||
addListValidation(sheet, 7, 1, 2, "0", "1");
|
||||
}
|
||||
if (statusDropdown) {
|
||||
if (statusAsList) {
|
||||
addListValidation(sheet, 8, 1, statusLastRow, "0", "1");
|
||||
} else {
|
||||
addIntegerValidation(sheet, 8, 1, 2);
|
||||
}
|
||||
}
|
||||
|
||||
workbook.write(outputStream);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] baseStaffDualSheetWorkbookWithMissingAssetStatusDropdown() throws Exception {
|
||||
try (Workbook workbook = new XSSFWorkbook();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Sheet staffSheet = workbook.createSheet("员工信息");
|
||||
Row staffHeader = staffSheet.createRow(0);
|
||||
String[] staffHeaders = {"姓名", "员工ID", "所属部门ID", "身份证号", "电话", "年收入(元/年)",
|
||||
"入职时间", "是否党员", "状态"};
|
||||
for (int i = 0; i < staffHeaders.length; i++) {
|
||||
staffHeader.createCell(i).setCellValue(staffHeaders[i]);
|
||||
}
|
||||
createBaseStaffRow(staffSheet, 1, "张三", 9020001L, "33010619850202101X", "0", "1");
|
||||
addListValidation(staffSheet, 7, 1, 1, "0", "1");
|
||||
addListValidation(staffSheet, 8, 1, 1, "0", "1");
|
||||
|
||||
Sheet assetSheet = workbook.createSheet("员工资产信息");
|
||||
Row assetHeader = assetSheet.createRow(0);
|
||||
String[] assetHeaders = {"员工身份证号*", "资产大类*", "资产小类*", "资产名称*", "产权占比",
|
||||
"购买/评估日期", "资产原值", "当前估值*", "估值截止日期", "资产状态*", "备注"};
|
||||
for (int i = 0; i < assetHeaders.length; i++) {
|
||||
assetHeader.createCell(i).setCellValue(assetHeaders[i]);
|
||||
}
|
||||
Row assetRow = assetSheet.createRow(1);
|
||||
assetRow.createCell(0).setCellValue("33010619850202101X");
|
||||
assetRow.createCell(1).setCellValue("房产");
|
||||
assetRow.createCell(2).setCellValue("住宅");
|
||||
assetRow.createCell(3).setCellValue("测试住宅");
|
||||
assetRow.createCell(7).setCellValue(1000000D);
|
||||
assetRow.createCell(9).setCellValue("正常");
|
||||
|
||||
workbook.write(outputStream);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void createBaseStaffRow(Sheet sheet, int rowIndex, String name, long staffId, String idCard,
|
||||
String partyMember, String status) {
|
||||
Row row = sheet.createRow(rowIndex);
|
||||
row.createCell(0).setCellValue(name);
|
||||
row.createCell(1).setCellValue(staffId);
|
||||
row.createCell(2).setCellValue(103L);
|
||||
row.createCell(3, CellType.STRING).setCellValue(idCard);
|
||||
row.createCell(4, CellType.STRING).setCellValue("13370000001");
|
||||
row.createCell(5).setCellValue(new BigDecimal("180000").doubleValue());
|
||||
row.createCell(6).setCellValue("2026-04-30");
|
||||
row.createCell(7, CellType.STRING).setCellValue(partyMember);
|
||||
row.createCell(8, CellType.STRING).setCellValue(status);
|
||||
}
|
||||
|
||||
private void addListValidation(Sheet sheet, int columnIndex, int firstRow, int lastRow, String... options) {
|
||||
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||
DataValidationConstraint constraint = helper.createExplicitListConstraint(options);
|
||||
DataValidation validation = helper.createValidation(
|
||||
constraint,
|
||||
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex)
|
||||
);
|
||||
sheet.addValidationData(validation);
|
||||
}
|
||||
|
||||
private void addIntegerValidation(Sheet sheet, int columnIndex, int firstRow, int lastRow) {
|
||||
DataValidationHelper helper = sheet.getDataValidationHelper();
|
||||
DataValidationConstraint constraint = helper.createIntegerConstraint(
|
||||
DataValidationConstraint.OperatorType.BETWEEN,
|
||||
"0",
|
||||
"1"
|
||||
);
|
||||
DataValidation validation = helper.createValidation(
|
||||
constraint,
|
||||
new CellRangeAddressList(firstRow, lastRow, columnIndex, columnIndex)
|
||||
);
|
||||
sheet.addValidationData(validation);
|
||||
}
|
||||
|
||||
private byte[] plainWorkbookWithoutDropdown() throws Exception {
|
||||
try (Workbook workbook = new XSSFWorkbook();
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||||
Sheet sheet = workbook.createSheet("普通信息");
|
||||
Row header = sheet.createRow(0);
|
||||
header.createCell(0).setCellValue("名称");
|
||||
Row row = sheet.createRow(1);
|
||||
row.createCell(0).setCellValue("张三");
|
||||
workbook.write(outputStream);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
public static class PlainExcel {
|
||||
@ExcelProperty(value = "名称", index = 0)
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.ruoyi.lsfx.client;
|
||||
|
||||
import com.ruoyi.lsfx.constants.LsfxConstants;
|
||||
import com.ruoyi.lsfx.domain.response.UploadFileResponse;
|
||||
import com.ruoyi.lsfx.util.HttpUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class LsfxAnalysisClientTest {
|
||||
|
||||
@Mock
|
||||
private HttpUtil httpUtil;
|
||||
|
||||
@InjectMocks
|
||||
private LsfxAnalysisClient client;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void uploadFile_shouldPassOriginalFilenameToMultipartResource() throws Exception {
|
||||
ReflectionTestUtils.setField(client, "baseUrl", "http://lsfx");
|
||||
ReflectionTestUtils.setField(client, "uploadFileEndpoint", "/upload");
|
||||
ReflectionTestUtils.setField(client, "clientId", "client-1");
|
||||
|
||||
Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
|
||||
Files.writeString(tempFile, "content");
|
||||
|
||||
UploadFileResponse response = new UploadFileResponse();
|
||||
response.setData(new UploadFileResponse.UploadData());
|
||||
|
||||
ArgumentCaptor<Map<String, Object>> paramsCaptor = ArgumentCaptor.forClass(Map.class);
|
||||
ArgumentCaptor<Map<String, String>> headersCaptor = ArgumentCaptor.forClass(Map.class);
|
||||
when(httpUtil.uploadFile(eq("http://lsfx/upload"), paramsCaptor.capture(), headersCaptor.capture(), eq(UploadFileResponse.class)))
|
||||
.thenReturn(response);
|
||||
|
||||
client.uploadFile(200, tempFile.toFile(), "银行流水A.xlsx");
|
||||
|
||||
assertEquals(200, paramsCaptor.getValue().get("groupId"));
|
||||
Resource filePart = assertInstanceOf(Resource.class, paramsCaptor.getValue().get("files"));
|
||||
assertEquals("银行流水A.xlsx", filePart.getFilename());
|
||||
assertEquals("client-1", headersCaptor.getValue().get(LsfxConstants.HEADER_CLIENT_ID));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.ruoyi.lsfx.util;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HttpUtilTest {
|
||||
|
||||
@Mock
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@TempDir
|
||||
Path tempDir;
|
||||
|
||||
@Test
|
||||
void uploadFile_shouldUseExplicitResourceFilename() throws Exception {
|
||||
HttpUtil httpUtil = new HttpUtil();
|
||||
ReflectionTestUtils.setField(httpUtil, "restTemplate", restTemplate);
|
||||
|
||||
Path tempFile = tempDir.resolve("batch_0_123456.xlsx");
|
||||
Files.writeString(tempFile, "content");
|
||||
|
||||
ArgumentCaptor<HttpEntity> captor = ArgumentCaptor.forClass(HttpEntity.class);
|
||||
when(restTemplate.postForEntity(eq("http://lsfx/upload"), captor.capture(), eq(String.class)))
|
||||
.thenReturn(ResponseEntity.ok("ok"));
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("groupId", 200);
|
||||
params.put("files", HttpUtil.namedFileResource(tempFile.toFile(), "银行流水A.xlsx"));
|
||||
|
||||
String result = httpUtil.uploadFile("http://lsfx/upload", params, null, String.class);
|
||||
|
||||
assertEquals("ok", result);
|
||||
MultiValueMap<String, Object> body = (MultiValueMap<String, Object>) captor.getValue().getBody();
|
||||
Object filePart = body.getFirst("files");
|
||||
Resource resource = assertInstanceOf(Resource.class, filePart);
|
||||
assertEquals("银行流水A.xlsx", resource.getFilename());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mock-maker-subclass
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiBankTagTaskMapperXmlTest {
|
||||
|
||||
private static final String RESOURCE = "mapper/ccdi/project/CcdiBankTagTaskMapper.xml";
|
||||
|
||||
@Test
|
||||
void mapper_shouldExposeLatestFailedTaskQuery() {
|
||||
Method method = Arrays.stream(CcdiBankTagTaskMapper.class.getDeclaredMethods())
|
||||
.filter(item -> "selectLatestFailedTaskByProjectId".equals(item.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
assertNotNull(method, "应提供查询项目最近失败打标任务的方法");
|
||||
}
|
||||
|
||||
@Test
|
||||
void xml_shouldSelectLatestFailedTaskByProjectId() throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(xml.contains("selectLatestFailedTaskByProjectId"), xml);
|
||||
assertTrue(xml.contains("from ccdi_bank_tag_task"), xml);
|
||||
assertTrue(xml.contains("project_id = #{projectId}"), xml);
|
||||
assertTrue(xml.contains("status = 'FAILED'"), xml);
|
||||
assertTrue(xml.contains("order by id desc"), xml);
|
||||
assertTrue(xml.contains("limit 1"), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiProjectExtendedPurchaseSupplierContractTest {
|
||||
|
||||
@Test
|
||||
void shouldExposeExtendedPurchaseSupplierDetailQuery() throws Exception {
|
||||
Class<?> mapperClass = Class.forName("com.ruoyi.ccdi.project.mapper.CcdiProjectSpecialCheckMapper");
|
||||
|
||||
Method method = mapperClass.getMethod("selectExtendedPurchaseSuppliers", Long.class, String.class);
|
||||
assertEquals(List.class, method.getReturnType());
|
||||
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectSpecialCheckMapper.xml"));
|
||||
assertTrue(xml.contains("select id=\"selectExtendedPurchaseSuppliers\""));
|
||||
assertTrue(xml.contains("ccdi_purchase_transaction_supplier"));
|
||||
assertTrue(xml.contains("is_bid_winner"));
|
||||
assertTrue(xml.contains("sort_order"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiProjectMapperXmlTest {
|
||||
|
||||
@Test
|
||||
void projectQueriesShouldApplyProjectVisibilityAndDeletedFilters() throws Exception {
|
||||
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectMapper.xml"));
|
||||
|
||||
assertFalse(xml.contains("params.dataScope"), xml);
|
||||
assertFalse(xml.contains("${queryDTO.params.dataScope}"), xml);
|
||||
|
||||
String listSql = extractSelect(xml, "selectProjectPage");
|
||||
assertTrue(listSql.contains("LEFT JOIN sys_user u ON p.create_by = u.user_name"), listSql);
|
||||
assertTrue(listSql.contains("scope != null and !scope.viewAllProjects"), listSql);
|
||||
assertTrue(listSql.contains("p.create_by = #{scope.username}"), listSql);
|
||||
assertTrue(listSql.contains("queryDTO.includeDeleted"), listSql);
|
||||
assertTrue(listSql.contains("p.del_flag = '2'"), listSql);
|
||||
assertTrue(listSql.contains("p.status = '5'"), listSql);
|
||||
assertTrue(listSql.contains("p.del_flag = '0'"), listSql);
|
||||
assertTrue(listSql.contains("p.status != '5'"), listSql);
|
||||
|
||||
String historySql = extractSelect(xml, "selectHistoryProjects");
|
||||
assertTrue(historySql.contains("p.status in ('1', '2')"), historySql);
|
||||
assertTrue(historySql.contains("p.del_flag = '0'"), historySql);
|
||||
|
||||
assertTrue(xml.contains("<update id=\"markProjectDeleted\">"), xml);
|
||||
assertTrue(xml.contains("status = '5'"), xml);
|
||||
assertTrue(xml.contains("del_flag = '2'"), xml);
|
||||
assertTrue(xml.contains("<update id=\"restoreDeletedProject\">"), xml);
|
||||
assertTrue(xml.contains("status = '1'"), xml);
|
||||
assertTrue(xml.contains("del_flag = '0'"), xml);
|
||||
assertTrue(xml.contains("is_archived = 0"), xml);
|
||||
}
|
||||
|
||||
private String extractSelect(String xml, String selectId) {
|
||||
String start = "<select id=\"" + selectId + "\"";
|
||||
int startIndex = xml.indexOf(start);
|
||||
assertTrue(startIndex >= 0, "missing select: " + selectId);
|
||||
int endIndex = xml.indexOf("</select>", startIndex);
|
||||
assertTrue(endIndex >= 0, "missing closing select tag: " + selectId);
|
||||
return xml.substring(startIndex, endIndex);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
mock-maker-subclass
|
||||
@@ -0,0 +1,138 @@
|
||||
# 2026-04-21 中介库管理导入功能浏览器测试记录
|
||||
|
||||
## 1. 测试目标
|
||||
|
||||
- 在真实浏览器中验证“中介库管理”页面两类导入功能:
|
||||
- 导入中介和亲属信息
|
||||
- 导入中介实体关联关系
|
||||
- 验证模板下载、模板改写、页面上传、异步导入结果展示、失败记录查看全链路可用。
|
||||
|
||||
## 2. 测试环境
|
||||
|
||||
- 前端地址:`http://localhost:62319`
|
||||
- 后端地址:`http://127.0.0.1:62318`
|
||||
- 登录方式:调用项目测试登录接口 `/login/test` 获取 token 后注入浏览器 Cookie
|
||||
- 浏览器:Playwright headed 模式
|
||||
|
||||
## 3. 下载模板
|
||||
|
||||
- 中介和亲属信息模板:
|
||||
- 浏览器下载原始文件:`.playwright-cli/中介和亲属信息导入模板-1776735200748.xlsx`
|
||||
- 中介实体关联关系模板:
|
||||
- 浏览器下载原始文件:`.playwright-cli/中介实体关联关系导入模板-1776735620019.xlsx`
|
||||
|
||||
## 4. 生成测试文件
|
||||
|
||||
- 中介和亲属信息首轮混合测试文件:
|
||||
- `output/spreadsheet/intermediary_person_import_browser_phase1.xlsx`
|
||||
- 中介和亲属信息二轮库内重复测试文件:
|
||||
- `output/spreadsheet/intermediary_person_import_browser_phase2_existing_db_cases.xlsx`
|
||||
- 中介实体关联关系首轮混合测试文件:
|
||||
- `output/spreadsheet/intermediary_enterprise_relation_import_browser_phase1.xlsx`
|
||||
- 中介实体关联关系二轮库内重复测试文件:
|
||||
- `output/spreadsheet/intermediary_enterprise_relation_import_browser_phase2_db_duplicate.xlsx`
|
||||
|
||||
## 5. 页面测试过程与结果
|
||||
|
||||
### 5.1 导入中介和亲属信息
|
||||
|
||||
#### 第一轮:混合成功/失败场景
|
||||
|
||||
- 上传文件:`output/spreadsheet/intermediary_person_import_browser_phase1.xlsx`
|
||||
- 页面导入结果:
|
||||
- 总数 `13` 条
|
||||
- 成功 `4` 条
|
||||
- 失败 `9` 条
|
||||
- 页面列表确认新增成功数据:
|
||||
- `自动化中介本人A`
|
||||
- `自动化中介A配偶`
|
||||
- `文件内重复本人1`
|
||||
- `文件内重复亲属1`
|
||||
|
||||
#### 第一轮失败记录命中情况
|
||||
|
||||
- `本人行关联中介本人证件号码必须为空`
|
||||
- `亲属行必须填写关联中介本人证件号码`
|
||||
- `姓名不能为空`
|
||||
- `人员子类型不能为空`
|
||||
- `证件号码身份证号长度必须为18位`
|
||||
- `中介本人证件号码[320101199503154560]在导入文件中重复`
|
||||
- `关联中介本人证件号码[320101197704077654]不存在`
|
||||
- `同一中介本人名下证件号码[320101199604102348]的亲属在导入文件中重复`
|
||||
|
||||
说明:
|
||||
|
||||
- 原计划用于命中“库内本人重复”的旧数据 `320101199904170833` 因系统内该历史证件号本身校验位不合法,页面实际先命中了证件号格式校验。
|
||||
|
||||
#### 第二轮:补齐库内重复分支
|
||||
|
||||
- 上传文件:`output/spreadsheet/intermediary_person_import_browser_phase2_existing_db_cases.xlsx`
|
||||
- 页面导入结果:
|
||||
- 总数 `2` 条
|
||||
- 成功 `0` 条
|
||||
- 失败 `2` 条
|
||||
- 页面失败记录确认命中:
|
||||
- `中介本人证件号码[46265019770816746X]已存在,请勿重复导入`
|
||||
- `同一中介本人名下证件号码[320101199902033213]的亲属已存在,请勿重复导入`
|
||||
|
||||
### 5.2 导入中介实体关联关系
|
||||
|
||||
#### 第一轮:混合成功/失败场景
|
||||
|
||||
- 上传文件:`output/spreadsheet/intermediary_enterprise_relation_import_browser_phase1.xlsx`
|
||||
- 页面导入结果:
|
||||
- 总数 `11` 条
|
||||
- 成功 `3` 条
|
||||
- 失败 `8` 条
|
||||
- 页面列表确认新增成功数据:
|
||||
- `成都市资产企业 / 自动化中介本人A / 董事`
|
||||
- `上海市资产企业 / 自动化中介本人A / 监事`
|
||||
- `杭州市不动产合伙企业 / 自动化中介本人A / 法人`
|
||||
|
||||
#### 第一轮失败记录命中情况
|
||||
|
||||
- `中介本人证件号码不能为空`
|
||||
- `中介本人证件号码身份证号长度必须为18位`
|
||||
- `中介本人不存在,请先导入或维护中介本人信息`
|
||||
- `统一社会信用代码不能为空`
|
||||
- `统一社会信用代码不存在于系统机构表`
|
||||
- `关联人职务长度不能超过100个字符`
|
||||
- `备注长度不能超过500个字符`
|
||||
- `同一中介本人与统一社会信用代码组合在导入文件中重复`
|
||||
|
||||
#### 第二轮:补齐库内关系重复分支
|
||||
|
||||
- 上传文件:`output/spreadsheet/intermediary_enterprise_relation_import_browser_phase2_db_duplicate.xlsx`
|
||||
- 页面导入结果:
|
||||
- 总数 `1` 条
|
||||
- 成功 `0` 条
|
||||
- 失败 `1` 条
|
||||
- 页面失败记录确认命中:
|
||||
- `中介实体关联关系已存在,请勿重复导入`
|
||||
|
||||
## 6. 结论
|
||||
|
||||
- 两类导入功能的模板下载、模板改写、页面上传、异步轮询、失败记录弹窗均可正常工作。
|
||||
- 中介和亲属信息导入已覆盖:
|
||||
- 成功导入
|
||||
- 本人行关联字段错误
|
||||
- 亲属缺少关联本人
|
||||
- 姓名为空
|
||||
- 人员子类型为空
|
||||
- 证件号非法
|
||||
- 文件内本人重复
|
||||
- 关联本人不存在
|
||||
- 文件内亲属重复
|
||||
- 库内本人重复
|
||||
- 库内亲属重复
|
||||
- 中介实体关联关系导入已覆盖:
|
||||
- 成功导入
|
||||
- 中介本人为空
|
||||
- 中介本人证件号非法
|
||||
- 中介本人不存在
|
||||
- 统一社会信用代码为空
|
||||
- 统一社会信用代码不存在
|
||||
- 关联人职务超长
|
||||
- 备注超长
|
||||
- 文件内关系重复
|
||||
- 库内关系重复
|
||||
@@ -0,0 +1,196 @@
|
||||
# 2026-04-22 员工信息维护真实页面综合测试记录
|
||||
|
||||
## 1. 测试目标
|
||||
|
||||
- 在真实浏览器中进入“信息维护-员工信息维护”页面,验证页面可访问、列表可加载、关键接口可正常返回。
|
||||
- 覆盖员工信息维护核心业务链路:
|
||||
- 列表加载
|
||||
- 详情查看
|
||||
- 新增
|
||||
- 编辑
|
||||
- 删除
|
||||
- 导入模板下载
|
||||
- 双 Sheet Excel 导入
|
||||
- 员工导入失败记录查看
|
||||
- 员工资产导入失败记录查看
|
||||
- 测试结束后清理本轮新增/导入成功数据,并清理页面本地导入缓存。
|
||||
|
||||
## 2. 测试环境
|
||||
|
||||
- 前端地址:`http://127.0.0.1:1025`
|
||||
- 后端地址:`http://127.0.0.1:62318`
|
||||
- Mock 服务:`http://127.0.0.1:8000`
|
||||
- 前端 Node 版本:`nvm use 14.21.3`
|
||||
- 浏览器:Playwright headed 模式真实浏览器
|
||||
- 登录方式:先调用测试登录接口获取 token,再向真实浏览器注入 `Admin-Token` cookie
|
||||
- 测试页面:`http://127.0.0.1:1025/maintain/baseStaff`
|
||||
|
||||
## 3. 测试文件
|
||||
|
||||
- 页面下载模板:
|
||||
- `/Users/wkc/Desktop/ccdi/ccdi/output/playwright/base-staff-maintenance-test/.playwright-cli/员工信息维护导入模板-1776851234493.xlsx`
|
||||
- 生成导入样本:
|
||||
- `/Users/wkc/Desktop/ccdi/ccdi/output/spreadsheet/base_staff_import_browser_mixed.xlsx`
|
||||
- Playwright 上传副本:
|
||||
- `/Users/wkc/Desktop/ccdi/ccdi/output/playwright/base-staff-maintenance-test/base_staff_import_browser_mixed.xlsx`
|
||||
|
||||
## 4. 页面测试过程与结果
|
||||
|
||||
### 4.1 页面进入与基础加载
|
||||
|
||||
- 进入“信息维护-员工信息维护”页面后,菜单高亮、面包屑、查询区、列表区展示正常。
|
||||
- Playwright 网络面板确认关键请求返回 `200`:
|
||||
- `/dev-api/getInfo`
|
||||
- `/dev-api/getRouters`
|
||||
- `/dev-api/ccdi/baseStaff/list`
|
||||
- `/dev-api/system/user/deptTree`
|
||||
- 页面列表初始总数为:
|
||||
- `共 1014 条`
|
||||
|
||||
### 4.2 新增
|
||||
|
||||
- 新增员工:
|
||||
- 姓名:`浏览器员工测试A-0422`
|
||||
- 柜员号:`9260422`
|
||||
- 所属部门:`若依科技`
|
||||
- 身份证号:`330101199206150012`
|
||||
- 电话:`13926042222`
|
||||
- 年收入:`246800.50`
|
||||
- 入职时间:`2026-04-22`
|
||||
- 是否党员:`是`
|
||||
- 状态:`在职`
|
||||
- 同时新增两条资产:
|
||||
- `房产 / 住宅 / 浏览器测试住房A / 当前估值 860000 / 状态 正常 / 备注 新增资产A`
|
||||
- `车辆 / 轿车 / 浏览器测试车辆B / 当前估值 188000 / 状态 正常 / 备注 新增资产B`
|
||||
- 保存后验证通过:
|
||||
- 新增员工进入列表首行
|
||||
- 列表总数从 `1014` 增加到 `1015`
|
||||
|
||||
### 4.3 新增后详情复验
|
||||
|
||||
- 打开新增员工详情后验证通过:
|
||||
- 基本信息可正常展示
|
||||
- 资产明细展示 2 条
|
||||
- 资产名称、估值、状态、备注与录入一致
|
||||
|
||||
### 4.4 编辑
|
||||
|
||||
- 编辑同一员工,修改内容:
|
||||
- 姓名改为 `浏览器员工测试A-0422-已改`
|
||||
- 电话改为 `13926042224`
|
||||
- 年收入改为 `268000`
|
||||
- 删除“房产 / 住宅”资产,仅保留“车辆 / 轿车”资产
|
||||
- 保留资产状态改为 `冻结`
|
||||
- 保留资产备注改为 `编辑后保留资产`
|
||||
- 保存后验证通过:
|
||||
- 列表首行姓名、电话、年收入已同步更新
|
||||
- 详情中仅剩 1 条原有资产
|
||||
- 保留资产状态显示 `冻结`
|
||||
- 保留资产备注显示 `编辑后保留资产`
|
||||
|
||||
### 4.5 导入模板下载
|
||||
|
||||
- 在真实页面导入弹窗中点击“下载模板”。
|
||||
- 下载结果正常,模板包含两个 Sheet:
|
||||
- `员工信息`
|
||||
- `员工资产信息`
|
||||
|
||||
### 4.6 双 Sheet 导入
|
||||
|
||||
- 使用 `base_staff_import_browser_mixed.xlsx` 发起双 Sheet 导入。
|
||||
- 员工信息 Sheet:
|
||||
- 成功样本:`浏览器导入员工B-0422 / 9260423`
|
||||
- 失败样本:`浏览器导入失败员工-0422 / 9033101`
|
||||
- 员工资产信息 Sheet:
|
||||
- 成功样本:向员工 `330101199206150012` 导入资产 `导入资产车位A-0422`
|
||||
- 失败样本:使用不存在证件号 `330101199206150063`
|
||||
- 导入提交后页面提示:
|
||||
- `导入任务已提交`
|
||||
- 导入完成后验证通过:
|
||||
- 页面出现“查看导入失败记录”
|
||||
- 页面出现“查看员工资产导入失败记录”
|
||||
- 成功导入员工 `9260423` 进入列表
|
||||
- 列表总数从 `1015` 增加到 `1016`
|
||||
|
||||
### 4.7 员工导入失败记录
|
||||
|
||||
- 打开“查看导入失败记录”弹窗。
|
||||
- 页面失败记录命中:
|
||||
- Sheet:`员工信息`
|
||||
- Excel 行号:`3`
|
||||
- 姓名:`浏览器导入失败员工-0422`
|
||||
- 柜员号:`9033101`
|
||||
- 失败原因:`该员工ID已存在`
|
||||
- 说明员工导入失败记录展示链路正常。
|
||||
|
||||
### 4.8 员工资产导入失败记录
|
||||
|
||||
- 打开“查看员工资产导入失败记录”弹窗。
|
||||
- 页面失败记录命中:
|
||||
- Sheet:`员工资产信息`
|
||||
- Excel 行号:`3`
|
||||
- 资产实际持有人身份证号:`330101199206150063`
|
||||
- 资产名称:`导入失败资产-0422`
|
||||
- 失败原因:`员工资产导入仅支持员工本人证件号`
|
||||
- 说明员工资产导入失败记录展示链路正常。
|
||||
|
||||
### 4.9 导入成功数据复验
|
||||
|
||||
- 再次打开员工 `9260422` 的详情。
|
||||
- 验证通过:
|
||||
- 新导入资产 `房产 / 车位 / 导入资产车位A-0422` 已展示
|
||||
- 当前估值显示 `235000`
|
||||
- 资产状态显示 `正常`
|
||||
- 备注显示 `页面上传成功资产`
|
||||
- 编辑后保留的车辆资产仍存在
|
||||
|
||||
### 4.10 删除与清理
|
||||
|
||||
- 通过真实页面删除以下两条本轮测试产生的成功数据:
|
||||
- 导入成功员工:`9260423`
|
||||
- 页面新增员工:`9260422`
|
||||
- 删除过程均经过二次确认弹窗,页面提示 `删除成功`。
|
||||
- 清理完成后验证通过:
|
||||
- 列表总数回到 `共 1014 条`
|
||||
- 页面中已无 `9260422`、`9260423`
|
||||
- 清理本地导入任务缓存:
|
||||
- 已移除 `localStorage` 中的 `employee_import_last_task`
|
||||
- 已移除 `localStorage` 中的 `employee_asset_import_last_task`
|
||||
- 页面刷新后:
|
||||
- “查看导入失败记录”按钮已消失
|
||||
- “查看员工资产导入失败记录”按钮已消失
|
||||
|
||||
## 5. 发现的问题
|
||||
|
||||
- 员工详情弹窗“所属部门”显示异常。
|
||||
- 复现方式:
|
||||
- 新增或编辑员工后,列表中“所属部门”正常显示为 `若依科技`
|
||||
- 打开同一员工详情,字段“所属部门”显示为 `-`
|
||||
- 影响判断:
|
||||
- 属于真实功能问题,影响详情信息一致性
|
||||
- 不影响本次新增、编辑、导入、删除主流程完成
|
||||
|
||||
## 6. 结论
|
||||
|
||||
- 员工信息维护模块在真实页面下的核心链路已完整验证:
|
||||
- 列表加载
|
||||
- 详情查看
|
||||
- 新增
|
||||
- 编辑
|
||||
- 删除
|
||||
- 导入模板下载
|
||||
- 双 Sheet 导入
|
||||
- 员工导入失败记录查看
|
||||
- 员工资产导入失败记录查看
|
||||
- 本轮测试发现 1 个非阻断性功能问题:
|
||||
- 员工详情弹窗所属部门未正确回显
|
||||
- 本轮新增和导入成功数据已全部清理,页面本地导入缓存已清空。
|
||||
|
||||
## 7. 测试收尾
|
||||
|
||||
- 已清理本轮新增/导入成功业务数据。
|
||||
- 已清理前端本地导入任务缓存。
|
||||
- 待测试完成后关闭:
|
||||
- Playwright 浏览器会话
|
||||
- 前端 `npm run dev -- --port 1025` 进程
|
||||
- 本轮通过 `bin/restart_java_backend.sh` 拉起的后端进程
|
||||
@@ -0,0 +1,170 @@
|
||||
# 2026-04-22 招投标信息维护真实页面综合测试记录
|
||||
|
||||
## 1. 测试目标
|
||||
|
||||
- 在真实浏览器中进入“信息维护-招投标信息维护”页面,验证页面可访问、列表可加载、无明显前端报错。
|
||||
- 覆盖招投标信息维护核心业务链路:
|
||||
- 列表加载
|
||||
- 条件查询
|
||||
- 详情查看
|
||||
- 新增
|
||||
- 编辑
|
||||
- 删除
|
||||
- 导入模板下载
|
||||
- 双 Sheet Excel 导入
|
||||
- 导入失败记录查看
|
||||
- 测试结束后清理本轮新增/导入成功数据,并关闭测试过程启动的前端进程与浏览器会话。
|
||||
|
||||
## 2. 测试环境
|
||||
|
||||
- 前端地址:`http://localhost:62319`
|
||||
- 后端地址:`http://127.0.0.1:62318`
|
||||
- Mock 服务:`http://127.0.0.1:8000`
|
||||
- 前端 Node 版本:`nvm use 14.21.3`
|
||||
- 浏览器:Playwright headed 模式真实浏览器
|
||||
- 登录方式:通过真实登录页使用预填测试账号 `admin / admin123` 登录后进入页面
|
||||
- 测试页面:`http://localhost:62319/maintain/purchaseTransaction`
|
||||
|
||||
## 3. 测试文件
|
||||
|
||||
- 页面下载模板:
|
||||
- `/Users/wkc/Desktop/ccdi/ccdi/output/playwright/bidding-maintenance-test/.playwright-cli/招投标信息维护导入模板-1776842565558.xlsx`
|
||||
- 生成导入样本:
|
||||
- `/Users/wkc/Desktop/ccdi/ccdi/output/spreadsheet/bidding_info_import_browser_mixed.xlsx`
|
||||
- Playwright 上传副本:
|
||||
- `/Users/wkc/Desktop/ccdi/ccdi/output/playwright/bidding-maintenance-test/bidding_info_import_browser_mixed.xlsx`
|
||||
|
||||
## 4. 页面测试过程与结果
|
||||
|
||||
### 4.1 页面进入与基础加载
|
||||
|
||||
- 从真实登录页登录后,通过顶部菜单进入“信息维护-招投标信息维护”。
|
||||
- 页面面包屑、菜单高亮、列表列头显示正常。
|
||||
- 关键列表列正常展示:
|
||||
- `中标供应商`
|
||||
- `参与供应商数`
|
||||
- `预算金额(元)`
|
||||
- Playwright 网络面板确认首屏关键请求均返回 `200`:
|
||||
- `/dev-api/getInfo`
|
||||
- `/dev-api/getRouters`
|
||||
- `/dev-api/ccdi/purchaseTransaction/list`
|
||||
- Playwright 控制台未发现 warning。
|
||||
|
||||
### 4.2 详情查看
|
||||
|
||||
- 打开首条现有记录详情:
|
||||
- `LSFXMOCKP2PUR001`
|
||||
- 验证通过:
|
||||
- 基本信息、金额信息、重要日期、申请人信息正常展示
|
||||
- 供应商明细表正常展示
|
||||
- 供应商排序、中标标识、统一信用代码、联系人、联系电话、银行账户展示正确
|
||||
|
||||
### 4.3 新增
|
||||
|
||||
- 新增测试单据:
|
||||
- 采购事项ID:`AUTOBID20260422151036`
|
||||
- 项目名称:`自动化招投标测试项目-1036`
|
||||
- 新增时录入两条供应商:
|
||||
- `杭州自动化供应商A有限公司`
|
||||
- `杭州自动化供应商B有限公司`
|
||||
- 将第 2 条供应商标记为中标后提交成功。
|
||||
- 列表校验通过:
|
||||
- 新增记录进入列表首行
|
||||
- `中标供应商` 显示为 `杭州自动化供应商B有限公司`
|
||||
- `参与供应商数` 显示为 `2`
|
||||
- `预算金额(元)`、申请人、申请部门、申请日期与录入一致
|
||||
|
||||
### 4.4 新增后详情复验
|
||||
|
||||
- 打开新增记录详情后验证通过:
|
||||
- 两条供应商都在详情表中展示
|
||||
- 第 1 条显示 `参标`
|
||||
- 第 2 条显示 `中标`
|
||||
- 排序、联系人、联系电话、银行账户与录入一致
|
||||
- 全部重要日期、采购负责人信息正确回显
|
||||
|
||||
### 4.5 编辑
|
||||
|
||||
- 编辑同一条测试单据,修改内容:
|
||||
- 项目名称改为 `自动化招投标测试项目-1036-已改`
|
||||
- 标的物名称改为 `服务器及配件-1036-复核`
|
||||
- 预算金额改为 `258000`
|
||||
- 将中标供应商从 `杭州自动化供应商B有限公司` 切换为 `杭州自动化供应商A有限公司`
|
||||
- 保存后列表校验通过:
|
||||
- 项目名称、标的物名称、预算金额已更新
|
||||
- `中标供应商` 摘要同步更新为 `杭州自动化供应商A有限公司`
|
||||
- `参与供应商数` 仍为 `2`
|
||||
|
||||
### 4.6 条件查询
|
||||
|
||||
- 以编辑后的项目名称 `自动化招投标测试项目-1036-已改` 进行查询。
|
||||
- 查询结果:
|
||||
- 分页总数显示 `共 1 条`
|
||||
- 列表首条采购事项ID为 `AUTOBID20260422151036`
|
||||
- 说明项目名称条件查询可正常命中目标记录。
|
||||
|
||||
### 4.7 导入模板下载
|
||||
|
||||
- 在真实页面导入弹窗中点击“下载模板”。
|
||||
- 下载结果正常,模板包含两个 Sheet:
|
||||
- `招投标主信息`
|
||||
- `供应商明细`
|
||||
|
||||
### 4.8 导入
|
||||
|
||||
- 使用自制双 Sheet 测试文件 `bidding_info_import_browser_mixed.xlsx` 发起导入。
|
||||
- 测试文件包含 2 个采购事项ID:
|
||||
- 成功样本:`IMPBID20260422152321A`
|
||||
- 失败样本:`IMPBID20260422152321B`
|
||||
- 导入后页面结果验证通过:
|
||||
- 成功样本进入列表
|
||||
- 其 `中标供应商` 显示为 `导入供应商乙有限公司`
|
||||
- 其 `参与供应商数` 显示为 `2`
|
||||
- 页面出现“查看导入失败记录”按钮
|
||||
|
||||
### 4.9 导入失败记录
|
||||
|
||||
- 打开“查看导入失败记录”弹窗。
|
||||
- 页面失败记录命中:
|
||||
- 采购事项ID:`IMPBID20260422152321B`
|
||||
- 项目名称:`导入失败项目-152321`
|
||||
- 标的物名称:`失败样例设备-152321`
|
||||
- 失败原因:`申请人工号必须为7位数字`
|
||||
- 说明导入失败记录分页、失败原因展示链路正常。
|
||||
|
||||
### 4.10 删除与清理
|
||||
|
||||
- 通过真实页面删除以下两条本轮测试产生的成功数据:
|
||||
- `AUTOBID20260422151036`
|
||||
- `IMPBID20260422152321A`
|
||||
- 删除时均经过页面二次确认弹窗,删除成功后搜索结果回到 `共 0 条`。
|
||||
- 清理本地导入任务缓存:
|
||||
- 已移除 `localStorage` 中的 `purchase_transaction_import_last_task`
|
||||
- 页面刷新后,“查看导入失败记录”按钮已消失。
|
||||
- 最终列表总数恢复为:
|
||||
- `共 2004 条`
|
||||
|
||||
## 5. 结论
|
||||
|
||||
- 招投标信息维护模块在真实页面下的核心链路测试通过。
|
||||
- 本次已覆盖:
|
||||
- 页面进入与真实登录
|
||||
- 列表展示
|
||||
- 条件查询
|
||||
- 详情查看
|
||||
- 新增
|
||||
- 编辑
|
||||
- 删除
|
||||
- 导入模板下载
|
||||
- 双 Sheet 导入
|
||||
- 导入失败记录查看
|
||||
- 本轮测试未发现阻断性缺陷。
|
||||
- 本轮新增和导入成功的数据已清理,列表总数已回到测试前水平。
|
||||
|
||||
## 6. 测试收尾
|
||||
|
||||
- 已清理本轮新增/导入成功业务数据。
|
||||
- 已清理前端本地导入任务缓存。
|
||||
- 待测试完成后关闭:
|
||||
- Playwright 浏览器会话
|
||||
- 前端 `npm run dev` 进程
|
||||
@@ -0,0 +1,50 @@
|
||||
# 员工亲属实体关联真实页面测试记录
|
||||
|
||||
## 测试时间
|
||||
|
||||
2026-04-24 13:32
|
||||
|
||||
## 测试范围
|
||||
|
||||
- 页面:员工亲属实体关联
|
||||
- 实际访问地址:`http://localhost:8080/maintain/staffEnterpriseRelation`
|
||||
- 后端接口代理:`http://localhost:8080/dev-api`
|
||||
- 登录账号:`admin`
|
||||
|
||||
## 环境信息
|
||||
|
||||
- 后端:本地 `62318` 端口已启动
|
||||
- 前端:`ruoyi-ui` 使用 `.nvmrc` 指定的 Node `14.21.3`,执行 `npm run dev -- --port 8080 --open false`
|
||||
- 浏览器:Playwright 启动本机 Google Chrome,访问真实业务页面
|
||||
|
||||
## 测试数据
|
||||
|
||||
- 选择的有效亲属:`320101199201010051 LSFX低收入亲属 / 导入验证员工20260317`
|
||||
- 测试统一社会信用代码:`91756899ABCDEFGH12`
|
||||
- 测试企业名称:`自动化测试亲属实体企业56899`
|
||||
- 测试职务:`自动化测试职务`
|
||||
- 编辑后职务:`自动化测试职务-已编辑`
|
||||
|
||||
## 测试步骤与结果
|
||||
|
||||
1. 打开真实业务页面,确认员工亲属实体关联列表加载成功。
|
||||
2. 点击“新增”,通过亲属身份证号下拉选择有效员工亲属,确认亲属姓名与关联员工自动带出。
|
||||
3. 填写统一社会信用代码、企业名称、职务、补充说明并提交,页面提示“新增成功”。
|
||||
4. 按企业名称搜索,确认新增记录展示在列表中,并包含企业名称、职务和关联员工信息。
|
||||
5. 打开“详情”,确认详情弹窗展示亲属身份证号、亲属姓名、关联员工、统一社会信用代码、企业名称、职务、状态、数据来源和补充说明。
|
||||
6. 点击“编辑”,修改职务后提交,页面提示“修改成功”,列表展示编辑后的职务。
|
||||
7. 再次新增同一亲属身份证号与同一统一社会信用代码组合,确认页面拦截并提示“该亲属身份证号和统一社会信用代码组合已存在”。
|
||||
8. 删除本轮新增记录,页面提示“删除成功”,完成测试数据清理。
|
||||
9. 测试结束后通过后端列表接口按测试企业名称复核,返回 `total = 0`,确认本轮测试数据已清理。
|
||||
|
||||
## 验证结论
|
||||
|
||||
- 员工亲属实体关联主链路通过。
|
||||
- 有效亲属下拉搜索、自动带出关联员工、新增、查询、详情、编辑、重复组合校验、删除清理均符合预期。
|
||||
- 测试过程中未发现浏览器控制台错误。
|
||||
- 测试过程中未发现失败请求。
|
||||
|
||||
## 测试产物
|
||||
|
||||
- 页面截图位于 `output/playwright/`,文件名前缀为 `staff-family-enterprise-*2026-04-24T05-32-31-510Z.png`。
|
||||
- 临时 Playwright 脚本位于 `output/playwright/staff-family-enterprise-relation-browser-test.js`,该目录为测试输出目录,不纳入 Git 提交范围。
|
||||
@@ -0,0 +1,60 @@
|
||||
# 员工亲属实体关联导入真实页面测试记录
|
||||
|
||||
## 测试时间
|
||||
|
||||
2026-04-24 13:38
|
||||
|
||||
## 测试范围
|
||||
|
||||
- 页面:员工亲属实体关联
|
||||
- 实际访问地址:`http://localhost:8080/maintain/staffEnterpriseRelation`
|
||||
- 导入接口:`/ccdi/staffEnterpriseRelation/importData`
|
||||
- 导入状态接口:`/ccdi/staffEnterpriseRelation/importStatus/{taskId}`
|
||||
- 失败记录接口:`/ccdi/staffEnterpriseRelation/importFailures/{taskId}`
|
||||
|
||||
## 环境信息
|
||||
|
||||
- 后端:本地 `62318` 端口既有进程
|
||||
- 前端:`ruoyi-ui` 使用 `.nvmrc` 指定的 Node `14.21.3`,执行 `npm run dev -- --port 8080 --open false`
|
||||
- 浏览器:Playwright 启动本机 Google Chrome,访问真实业务页面
|
||||
|
||||
## 测试数据
|
||||
|
||||
- 有效亲属:`320101199201010051 LSFX低收入亲属 / 导入验证员工20260317`
|
||||
- 成功记录统一社会信用代码:`91141873ABCDEFGH12`
|
||||
- 成功记录企业名称:`导入测试亲属实体企业141873`
|
||||
- 成功记录职务:`导入测试职务`
|
||||
- 失败记录场景:同一导入文件内重复 `亲属身份证号 + 统一社会信用代码` 组合
|
||||
|
||||
## 测试步骤与结果
|
||||
|
||||
1. 打开真实业务页面,点击“导入”,确认弹出“员工亲属实体关联数据导入”弹窗。
|
||||
2. 在导入弹窗中点击“下载模板”,下载当前页面模板文件。
|
||||
3. 基于下载的模板生成测试文件,写入两条数据:
|
||||
- 第 1 条为有效亲属 + 唯一统一社会信用代码,预期成功。
|
||||
- 第 2 条与第 1 条使用相同亲属身份证号和统一社会信用代码,预期文件内重复失败。
|
||||
4. 在真实页面上传该测试文件并点击“确定”,页面提示“员工亲属实体关联导入任务已提交”。
|
||||
5. 等待页面异步轮询完成,出现“查看导入失败记录”按钮,说明本轮为部分成功。
|
||||
6. 按成功记录企业名称查询列表,确认成功导入记录展示在列表中。
|
||||
7. 打开成功记录详情,确认统一社会信用代码 `91141873ABCDEFGH12` 与导入来源展示正确。
|
||||
8. 点击“查看导入失败记录”,确认失败记录弹窗展示重复组合错误:`组合在导入文件中重复`。
|
||||
9. 删除本轮成功导入记录,页面提示“删除成功”。
|
||||
10. 测试结束后通过后端列表接口按测试企业名称复核,返回 `total = 0`,确认本轮成功写入数据已清理。
|
||||
|
||||
## 验证结论
|
||||
|
||||
- 导入模板下载正常。
|
||||
- 基于当前模板生成的导入文件可被页面上传并提交。
|
||||
- 异步导入轮询、部分成功提示、失败记录按钮恢复与失败记录弹窗均符合预期。
|
||||
- 成功记录可入库并在列表/详情中查询到。
|
||||
- 文件内重复组合被正确拦截,失败原因展示准确。
|
||||
- 测试过程中未发现浏览器控制台错误。
|
||||
- 测试过程中未发现失败请求。
|
||||
- 本轮成功写入的测试数据已删除清理。
|
||||
|
||||
## 测试产物
|
||||
|
||||
- 下载模板:`output/playwright/staff-family-enterprise-import-template-2026-04-24T05-38-57-495Z.xlsx`
|
||||
- 导入样本:`output/playwright/staff-family-enterprise-import-sample-2026-04-24T05-38-57-495Z.xlsx`
|
||||
- 页面截图位于 `output/playwright/`,文件名前缀为 `staff-family-enterprise-import-*2026-04-24T05-38-57-495Z.png`。
|
||||
- 临时 Playwright 脚本位于 `output/playwright/staff-family-enterprise-relation-import-browser-test.js`,该目录为测试输出目录,不纳入 Git 提交范围。
|
||||
@@ -0,0 +1,84 @@
|
||||
# 员工亲属实体关联导入全场景真实页面测试记录
|
||||
|
||||
## 测试时间
|
||||
|
||||
2026-04-26 16:02
|
||||
|
||||
## 测试范围
|
||||
|
||||
- 页面:员工亲属实体关联
|
||||
- 实际访问地址:`http://localhost:8080/maintain/staffEnterpriseRelation`
|
||||
- 导入接口:`/ccdi/staffEnterpriseRelation/importData`
|
||||
- 导入状态接口:`/ccdi/staffEnterpriseRelation/importStatus/{taskId}`
|
||||
- 失败记录接口:`/ccdi/staffEnterpriseRelation/importFailures/{taskId}`
|
||||
|
||||
## 环境信息
|
||||
|
||||
- 后端:本地 `62318` 端口既有进程
|
||||
- 前端:`ruoyi-ui` 使用 `.nvmrc` 指定的 Node `14.21.3`,执行 `npm run dev -- --port 8080 --open false`
|
||||
- 浏览器:Playwright 启动本机 Google Chrome,访问真实业务页面
|
||||
|
||||
## 前置数据
|
||||
|
||||
- 有效亲属:`320101199201010051 LSFX低收入亲属 / 导入验证员工20260317`
|
||||
- 临时无效亲属:`320101199801010421`
|
||||
- 测试企业名前缀:`全场景导入测试企业520344`
|
||||
|
||||
## 覆盖场景
|
||||
|
||||
1. 空模板
|
||||
- 行数:0
|
||||
- 预期:页面提示 `至少需要一条数据`
|
||||
- 结果:通过
|
||||
|
||||
2. 基础校验失败
|
||||
- 行数:9
|
||||
- 覆盖:
|
||||
- 亲属身份证号为空
|
||||
- 统一社会信用代码为空
|
||||
- 企业名称为空
|
||||
- 亲属身份证号格式错误
|
||||
- 统一社会信用代码格式错误
|
||||
- 亲属身份证号不存在
|
||||
- 亲属身份证号不是有效员工亲属
|
||||
- 企业名称长度超过 200 个字符
|
||||
- 关联人在企业的职务长度超过 100 个字符
|
||||
- 结果:成功 0 条,失败 9 条,失败记录弹窗逐项展示对应原因
|
||||
|
||||
3. 全成功
|
||||
- 行数:3
|
||||
- 结果:成功 3 条,失败 0 条,列表可查询到成功导入记录
|
||||
|
||||
4. 成功失败混合
|
||||
- 行数:5
|
||||
- 覆盖:
|
||||
- 2 条有效数据成功导入
|
||||
- 文件内重复 `亲属身份证号 + 统一社会信用代码` 组合
|
||||
- 亲属身份证号不存在
|
||||
- 统一社会信用代码格式错误
|
||||
- 结果:成功 2 条,失败 3 条,成功记录可查询,失败记录弹窗展示对应原因
|
||||
|
||||
5. 已存在组合
|
||||
- 行数:1
|
||||
- 前置:使用混合场景已成功导入的组合
|
||||
- 预期:拦截已存在的 `亲属身份证号 + 统一社会信用代码` 组合
|
||||
- 结果:成功 0 条,失败 1 条,失败记录展示 `组合已存在,请勿重复导入`
|
||||
|
||||
## 验证结论
|
||||
|
||||
- 导入模板下载正常。
|
||||
- 基于当前页面模板生成的多份测试文件均可在真实页面上传。
|
||||
- 空模板、基础字段校验、有效亲属校验、文件内重复、库内已存在组合、全成功、成功失败混合均已覆盖。
|
||||
- 异步导入轮询、成功统计、失败统计、失败记录按钮和失败记录弹窗均符合预期。
|
||||
- 成功导入记录能够在列表查询到。
|
||||
- 本轮成功写入的 5 条测试数据已删除。
|
||||
- 本轮临时新增的无效亲属前置数据已删除。
|
||||
- 后端接口复核测试企业名前缀返回 `total = 0`。
|
||||
- 浏览器控制台无错误。
|
||||
- 测试过程中无失败请求。
|
||||
|
||||
## 测试产物
|
||||
|
||||
- 下载模板:`output/playwright/staff-family-enterprise-allcases-template-2026-04-26T08-02-00-344Z.xlsx`
|
||||
- 场景样本文件与截图位于 `output/playwright/`,文件名前缀为 `staff-family-enterprise-allcases-*2026-04-26T08-02-00-344Z`。
|
||||
- 临时 Playwright 脚本位于 `output/playwright/staff-family-enterprise-relation-import-all-cases-browser-test.js`,该目录为测试输出目录,不纳入 Git 提交范围。
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.ruoyi.web.controller.system;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import com.ruoyi.common.utils.PasswordStrengthUtils;
|
||||
|
||||
class PasswordStrengthUtilsTest
|
||||
{
|
||||
@Test
|
||||
void shouldAcceptStrongPassword()
|
||||
{
|
||||
assertTrue(PasswordStrengthUtils.isStrongPassword("Abc123@#"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectPasswordWithoutRequiredCharacterTypes()
|
||||
{
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("abc123@#"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("ABC123@#"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abcdef@#"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abc12345"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectPasswordWithInvalidLengthOrIllegalCharacters()
|
||||
{
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword(null));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Ab1@"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abc1234567890123456@#"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abc 123@"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abc123<@"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abc123\\@"));
|
||||
assertFalse(PasswordStrengthUtils.isStrongPassword("Abc123|@"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
|
||||
class RedisConfigTest
|
||||
{
|
||||
@Test
|
||||
void shouldEnableValidateConnectionForLettuceConnectionFactory()
|
||||
{
|
||||
BeanPostProcessor beanPostProcessor = RedisConfig.lettuceConnectionFactoryBeanPostProcessor();
|
||||
LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(
|
||||
new RedisStandaloneConfiguration("127.0.0.1", 6379));
|
||||
|
||||
beanPostProcessor.postProcessBeforeInitialization(connectionFactory, "redisConnectionFactory");
|
||||
|
||||
assertThat(connectionFactory.getValidateConnection()).isTrue();
|
||||
}
|
||||
}
|
||||
29
ruoyi-ui/tests/unit/project-create-upload-jump.test.js
Normal file
29
ruoyi-ui/tests/unit/project-create-upload-jump.test.js
Normal file
@@ -0,0 +1,29 @@
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const indexPath = path.resolve(__dirname, "../../src/views/ccdiProject/index.vue");
|
||||
const indexSource = fs.readFileSync(indexPath, "utf8");
|
||||
|
||||
assert(
|
||||
indexSource.includes("handleSubmitProject(data)"),
|
||||
"项目创建成功回调应接收创建接口返回的数据"
|
||||
);
|
||||
|
||||
assert(
|
||||
indexSource.includes("path: `/ccdiProject/detail/${data.projectId}`"),
|
||||
"项目创建成功后应跳转到新项目详情页"
|
||||
);
|
||||
|
||||
assert(
|
||||
indexSource.includes('query: { tab: "upload" }') ||
|
||||
indexSource.includes("query: { tab: 'upload' }"),
|
||||
"项目创建成功后应显式激活上传数据页签"
|
||||
);
|
||||
|
||||
assert(
|
||||
!/handleSubmitProject\(data\)\s*\{[\s\S]*?this\.getList\(\)\s*\/\/ 刷新列表[\s\S]*?\}/.test(indexSource),
|
||||
"项目创建成功后不应只刷新列表"
|
||||
);
|
||||
|
||||
console.log("project-create-upload-jump test passed");
|
||||
78
ruoyi-ui/tests/unit/project-tag-failed-status.test.js
Normal file
78
ruoyi-ui/tests/unit/project-tag-failed-status.test.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
function readSource(relativePath) {
|
||||
return fs.readFileSync(path.resolve(__dirname, "../../", relativePath), "utf8");
|
||||
}
|
||||
|
||||
const detailSource = readSource("src/views/ccdiProject/detail.vue");
|
||||
const tableSource = readSource("src/views/ccdiProject/components/ProjectTable.vue");
|
||||
const searchBarSource = readSource("src/views/ccdiProject/components/SearchBar.vue");
|
||||
const indexSource = readSource("src/views/ccdiProject/index.vue");
|
||||
const uploadSource = readSource("src/views/ccdiProject/components/detail/UploadData.vue");
|
||||
const paramSource = readSource("src/views/ccdiProject/components/detail/ParamConfig.vue");
|
||||
|
||||
assert(
|
||||
detailSource.includes("latestTagTaskErrorMessage") &&
|
||||
detailSource.includes("latestTagTaskEndTime"),
|
||||
"项目详情接口字段应承载最近失败任务的错误信息和结束时间"
|
||||
);
|
||||
|
||||
assert(
|
||||
detailSource.includes("tagFailureDialogVisible") &&
|
||||
detailSource.includes("项目打标失败") &&
|
||||
detailSource.includes("查看完整错误"),
|
||||
"项目详情页应展示打标失败提示并提供完整错误弹窗"
|
||||
);
|
||||
|
||||
assert(
|
||||
/isProjectTagFailed\(\)\s*\{\s*return String\(this\.projectInfo\.projectStatus\) === "4";\s*\}/.test(
|
||||
detailSource
|
||||
),
|
||||
"项目详情页应显式识别 4-打标失败状态"
|
||||
);
|
||||
|
||||
assert(
|
||||
/4:\s*"danger"/.test(detailSource) && /4:\s*"打标失败"/.test(detailSource),
|
||||
"项目详情页状态映射应支持 4-打标失败"
|
||||
);
|
||||
|
||||
assert(
|
||||
/String\(this\.projectInfo\.projectStatus\)\s*!==\s*"3"/.test(detailSource),
|
||||
"项目状态轮询应在状态变为打标失败后停止"
|
||||
);
|
||||
|
||||
assert(
|
||||
tableSource.includes("['0', '3', '4'].includes(scope.row.status)"),
|
||||
"项目列表中打标失败项目应只开放进入项目入口"
|
||||
);
|
||||
|
||||
assert(
|
||||
!tableSource.includes("latestTagTaskErrorMessage"),
|
||||
"项目列表不应展示完整打标失败错误"
|
||||
);
|
||||
|
||||
assert(
|
||||
searchBarSource.includes("{ label: '打标失败', value: '4', count: 0 }"),
|
||||
"项目筛选栏应提供打标失败筛选项"
|
||||
);
|
||||
|
||||
assert(
|
||||
indexSource.includes("'4': counts.status4 || 0"),
|
||||
"项目首页应接收 status4 统计"
|
||||
);
|
||||
|
||||
assert(
|
||||
uploadSource.includes('return ["0", "3", "4"].includes(String(this.projectInfo.projectStatus));'),
|
||||
"上传数据页应将打标失败按进行中口径禁用报告入口"
|
||||
);
|
||||
|
||||
assert(
|
||||
!uploadSource.includes("isProjectTagFailed") &&
|
||||
!paramSource.includes("isProjectTagFailed") &&
|
||||
!paramSource.includes('projectStatus) === "4"'),
|
||||
"上传和参数配置页不应把打标失败作为只读锁定状态"
|
||||
);
|
||||
|
||||
console.log("project-tag-failed-status test passed");
|
||||
@@ -0,0 +1,51 @@
|
||||
const assert = require("assert");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const componentPath = path.resolve(
|
||||
__dirname,
|
||||
"../../src/views/ccdiPurchaseTransaction/index.vue"
|
||||
);
|
||||
const source = fs.readFileSync(componentPath, "utf8");
|
||||
|
||||
[
|
||||
'from "@/api/ccdiEnterpriseBaseInfo"',
|
||||
'from "@/api/ccdiEnum"',
|
||||
"enterpriseDetailOpen",
|
||||
"enterpriseDetailLoading",
|
||||
"enterpriseDetailData",
|
||||
"handleSupplierEnterpriseDetail(row)",
|
||||
"resetEnterpriseDetail()",
|
||||
"暂无企业信息"
|
||||
].forEach((token) => {
|
||||
assert(source.includes(token), `招投标供应商企业详情缺少关键结构: ${token}`);
|
||||
});
|
||||
|
||||
[
|
||||
'label="操作"',
|
||||
">详情</el-button>",
|
||||
"企业信息详情",
|
||||
"统一社会信用代码",
|
||||
"企业名称",
|
||||
"企业类型",
|
||||
"企业性质",
|
||||
"行业分类",
|
||||
"所属行业",
|
||||
"法定代表人",
|
||||
"风险等级",
|
||||
"企业来源",
|
||||
"数据来源",
|
||||
"股东5"
|
||||
].forEach((token) => {
|
||||
assert(source.includes(token), `招投标供应商企业详情模板缺少关键结构: ${token}`);
|
||||
});
|
||||
|
||||
[
|
||||
"v-hasPermi=\\\"['ccdi:enterpriseBaseInfo:query']\\\"",
|
||||
"v-hasPermi=\"['ccdi:enterpriseBaseInfo:query']\"",
|
||||
"ccdi:enterpriseBaseInfo:query"
|
||||
].forEach((token) => {
|
||||
assert(!source.includes(token), `本次不应新增实体库权限显隐控制: ${token}`);
|
||||
});
|
||||
|
||||
console.log("purchase-transaction-enterprise-detail-ui test passed");
|
||||
Reference in New Issue
Block a user