Index: ssts-web/src/test/java/test/forgon/disinfectsystem/reports/InstrumentSetDemandReportHelperTests.java =================================================================== diff -u --- ssts-web/src/test/java/test/forgon/disinfectsystem/reports/InstrumentSetDemandReportHelperTests.java (revision 0) +++ ssts-web/src/test/java/test/forgon/disinfectsystem/reports/InstrumentSetDemandReportHelperTests.java (revision 41495) @@ -0,0 +1,541 @@ +package test.forgon.disinfectsystem.reports; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.when; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import com.forgon.databaseadapter.service.DateQueryAdapter; +import com.forgon.directory.model.BrancheOfHospital; +import com.forgon.directory.service.BrancheOfHospitalManager; +import com.forgon.disinfectsystem.jasperreports.javabeansource.InstrumentSetDemandReportVo; +import com.forgon.disinfectsystem.jasperreports.util.InstrumentSetDemandReportHelper; +import com.forgon.tools.hibernate.ObjectDao; +public class InstrumentSetDemandReportHelperTests { + @Mock + private ObjectDao objectDao; + + @Mock + private BrancheOfHospitalManager brancheOfHospitalManager; + @Mock + private DateQueryAdapter dateQueryAdapter; + @Mock + private ResultSet resultSet; + + @InjectMocks + private InstrumentSetDemandReportHelper instrumentSetDemandReportHelper; + + private Map requestParameters; + private Map parametMap; + private AutoCloseable closeable; + + @Before + public void setUp() { + closeable = MockitoAnnotations.openMocks(this); + requestParameters = new HashMap<>(); + parametMap = new HashMap<>(); + } + + @After + public void tearDown() throws Exception { + closeable.close(); + } + + /** + * 测试用例 1: 基本功能测试 - 正常情况 + */ + @Test + public void testGetInstrumentSetDemandReportData_NormalCase() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + requestParameters.put("depart", "手术室"); + requestParameters.put("tousseName", "心脏手术器械包"); + + // Mock 数据库查询结果 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, false); + mockResultSetData(); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证结果 + assertNotNull(result); + assertEquals(2, result.size()); + + // 新增:验证总数统计字段存在 + assertTrue(parametMap.containsKey("totalTotalAmount")); + assertTrue(parametMap.containsKey("totalStockAmount")); + assertTrue(parametMap.containsKey("totalExpectedGapAmount")); + + // 验证排序(按缺口金额降序)- 使用VO的getter方法 + InstrumentSetDemandReportVo firstRecord = result.get(0); + Integer firstGap = firstRecord.getExpectedGapAmount(); + InstrumentSetDemandReportVo secondRecord = result.get(1); + Integer secondGap = secondRecord.getExpectedGapAmount(); + assertTrue(firstGap >= secondGap); + } + + /** + * 测试用例 2: 院区过滤测试 + */ + @Test + public void testGetInstrumentSetDemandReportData_WithBranchFilter() throws Exception { + // 准备测试数据 - 包含院区过滤 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + requestParameters.put("brancheOfHospitalIds", "1;2;3"); + + // Mock 院区数据 + Map departToBranchMap = new HashMap<>(); + BrancheOfHospital branch = new BrancheOfHospital(); + branch.setName("总院"); + departToBranchMap.put("手术室", branch); + + when(brancheOfHospitalManager.getDepartToBrancheOfHospitalMap(anySet())).thenReturn(departToBranchMap); + + // Mock 数据库查询结果 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, false); + mockResultSetData(); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证结果包含院区信息 - 使用VO的getter方法 + assertNotNull(result); + assertEquals(1, result.size()); + InstrumentSetDemandReportVo record = result.get(0); + assertEquals("总院", record.getBrancheOfHospitalName()); + } + + /** + * 测试用例 5: 库存充足情况测试 + */ + @Test + public void testGetInstrumentSetDemandReportData_StockSufficient() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 数据库查询结果 - 库存充足 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, false); + + // Mock 结果集数据 - 需求量小 + when(resultSet.getString("operationTime")).thenReturn("2024-01-01"); + when(resultSet.getString("depart")).thenReturn("手术室"); + when(resultSet.getString("tousseName")).thenReturn("小手术器械包"); + when(resultSet.getString("operationName")).thenReturn("小手术"); + when(resultSet.getInt("amount")).thenReturn(5); // 需求量小 + when(resultSet.getLong("ipId")).thenReturn(1001L); + + // Mock 库存查询 - 库存充足 + when(objectDao.countBySql(contains("小手术器械包"))).thenReturn(20); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证库存充足的情况 - 使用VO的getter方法 + assertNotNull(result); + assertEquals(1, result.size()); + InstrumentSetDemandReportVo record = result.get(0); + assertEquals(0, record.getExpectedGapAmount()); + assertEquals("否", record.getIsUrgent()); + } + + /** + * 测试用例 6: 库存不足紧急情况测试 + */ + @Test + public void testGetInstrumentSetDemandReportData_StockInsufficient() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 数据库查询结果 - 需求量大 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, false); + + when(resultSet.getString("operationTime")).thenReturn("2024-01-01"); + when(resultSet.getString("depart")).thenReturn("手术室"); + when(resultSet.getString("tousseName")).thenReturn("大手术器械包"); + when(resultSet.getString("operationName")).thenReturn("大手术"); + when(resultSet.getInt("amount")).thenReturn(50); // 需求量大 + when(resultSet.getLong("ipId")).thenReturn(1002L); + + // Mock 库存查询 - 库存不足 + when(objectDao.countBySql(contains("大手术器械包"))).thenReturn(10); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证库存不足的紧急情况 - 使用VO的getter方法 + assertNotNull(result); + assertEquals(1, result.size()); + InstrumentSetDemandReportVo record = result.get(0); + assertEquals(40, record.getExpectedGapAmount()); // 50-10=40 + assertEquals("是", record.getIsUrgent()); + } + + /** + * 测试用例 7: 数据分组和排重测试 + */ + @Test + public void testGetInstrumentSetDemandReportData_DataGrouping() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 多条相同分组的数据 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, true, false); // 三条相同分组记录 + + // 相同分组键的数据 + when(resultSet.getString("operationTime")).thenReturn("2024-01-01", "2024-01-01", "2024-01-01"); + when(resultSet.getString("depart")).thenReturn("手术室", "手术室", "手术室"); + when(resultSet.getString("tousseName")).thenReturn("测试器械包", "测试器械包", "测试器械包"); + when(resultSet.getString("operationName")).thenReturn("手术A", "手术B", "手术A"); // 重复的手术名称 + when(resultSet.getInt("amount")).thenReturn(10, 15, 5); // 总需求应为30 + when(resultSet.getLong("ipId")).thenReturn(1001L, 1002L, 1001L); // 重复的ipId + + // Mock 库存查询 + when(objectDao.countBySql(anyString())).thenReturn(25); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证数据分组和排重 - 使用VO的getter方法 + assertNotNull(result); + assertEquals(1, result.size()); // 应该只有一条分组记录 + + InstrumentSetDemandReportVo record = result.get(0); + String operationNames = record.getOperationNames(); + assertTrue(operationNames.contains("手术A")); + assertTrue(operationNames.contains("手术B")); + assertEquals(30, record.getTotalAmount()); // 10+15+5=30 + assertEquals(2, record.getIpCount()); // 两个不同的ipId + } + + /** + * 测试用例 9: 库存查询异常测试 + */ + @Test + public void testGetInstrumentSetDemandReportData_StockQueryException() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 主查询结果 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, false); + mockResultSetData(); + + // Mock 库存查询异常 + when(objectDao.countBySql(anyString())).thenThrow(new RuntimeException("库存查询失败")); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证在库存查询异常时能正常处理(返回0库存)- 使用VO的getter方法 + assertNotNull(result); + assertEquals(1, result.size()); + InstrumentSetDemandReportVo record = result.get(0); + assertEquals(0, record.getStockAmount()); + } + + /** + * 辅助方法:Mock结果集数据 + */ + private void mockResultSetData() throws SQLException { + when(resultSet.getString("operationTime")).thenReturn("2024-01-01", "2024-01-02"); + when(resultSet.getString("depart")).thenReturn("手术室", "急诊科"); + when(resultSet.getString("tousseName")).thenReturn("心脏手术器械包", "急诊器械包"); + when(resultSet.getString("operationName")).thenReturn("心脏搭桥手术", "急诊清创"); + when(resultSet.getInt("amount")).thenReturn(20, 15); + when(resultSet.getLong("ipId")).thenReturn(1001L, 1002L); + + // Mock 库存查询 + when(objectDao.countBySql(anyString())).thenReturn(10, 8); + } + + /** + * 测试用例 10: 排序逻辑测试 - 按缺口和总量排序 + */ + @Test + public void testGetInstrumentSetDemandReportData_SortingLogic() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 多条不同缺口的数据 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, true, false); + + // 三条记录,缺口分别为:15, 5, 10 + mockSortingTestData(); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证排序:按缺口降序,缺口相同时按总量降序 - 使用VO的getter方法 + assertNotNull(result); + assertEquals(3, result.size()); + + // 第一条记录缺口最大 + assertEquals(15, result.get(0).getExpectedGapAmount()); + // 第二条记录缺口次之 + assertEquals(10, result.get(1).getExpectedGapAmount()); + // 第三条记录缺口最小 + assertEquals(5, result.get(2).getExpectedGapAmount()); + } + + /** + * 辅助方法:Mock排序测试数据 + */ + private void mockSortingTestData() throws SQLException { + // 记录1: 缺口15 (需求30-库存15) + when(resultSet.getString("operationTime")).thenReturn("2024-01-01", "2024-01-02", "2024-01-03"); + when(resultSet.getString("depart")).thenReturn("手术室", "急诊科", "ICU"); + when(resultSet.getString("tousseName")).thenReturn("器械包A", "器械包B", "器械包C"); + when(resultSet.getString("operationName")).thenReturn("手术A", "手术B", "手术C"); + when(resultSet.getInt("amount")).thenReturn(30, 20, 15); // 需求量 + when(resultSet.getLong("ipId")).thenReturn(1001L, 1002L, 1003L); + + // Mock 库存查询结果 + when(objectDao.countBySql(contains("器械包A"))).thenReturn(15); + when(objectDao.countBySql(contains("器械包B"))).thenReturn(10); + when(objectDao.countBySql(contains("器械包C"))).thenReturn(10); + } + + /** + * 测试用例 11: 总数统计功能测试 + */ + @Test + public void testGetInstrumentSetDemandReportData_TotalAmountCalculation() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 数据库查询结果 - 多条记录 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, true, false); // 三条记录 + + // Mock 三条记录的数据 + when(resultSet.getString("operationTime")).thenReturn("2024-01-01", "2024-01-02", "2024-01-03"); + when(resultSet.getString("depart")).thenReturn("手术室", "急诊科", "ICU"); + when(resultSet.getString("tousseName")).thenReturn("器械包A", "器械包B", "器械包C"); + when(resultSet.getString("operationName")).thenReturn("手术A", "手术B", "手术C"); + when(resultSet.getInt("amount")).thenReturn(30, 20, 15); // 总需求 = 30+20+15 = 65 + when(resultSet.getLong("ipId")).thenReturn(1001L, 1002L, 1003L); + + // Mock 库存查询结果 + when(objectDao.countBySql(contains("器械包A"))).thenReturn(10); // 缺口20 + when(objectDao.countBySql(contains("器械包B"))).thenReturn(25); // 无缺口 + when(objectDao.countBySql(contains("器械包C"))).thenReturn(5); // 缺口10 + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证总数统计 + assertNotNull(result); + assertEquals(3, result.size()); + + // 验证总数统计字段 + assertEquals(65, parametMap.get("totalTotalAmount")); // 总需求: 30+20+15=65 + assertEquals(40, parametMap.get("totalStockAmount")); // 总库存: 10+25+5=40 + assertEquals(30, parametMap.get("totalExpectedGapAmount")); // 总缺口: 20+0+10=30 + + // 验证单个记录的缺口计算 - 使用VO的getter方法 + InstrumentSetDemandReportVo record1 = findRecordByTousseName(result, "器械包A"); + assertEquals(20, record1.getExpectedGapAmount()); // 30-10=20 + assertEquals("是", record1.getIsUrgent()); + + InstrumentSetDemandReportVo record2 = findRecordByTousseName(result, "器械包B"); + assertEquals(0, record2.getExpectedGapAmount()); // 20-25=-5,取0 + assertEquals("否", record2.getIsUrgent()); + + InstrumentSetDemandReportVo record3 = findRecordByTousseName(result, "器械包C"); + assertEquals(10, record3.getExpectedGapAmount()); // 15-5=10 + assertEquals("是", record3.getIsUrgent()); + } + + /** + * 测试用例 12: 无缺口情况的总数统计 + */ + @Test + public void testGetInstrumentSetDemandReportData_NoGapTotalCalculation() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 数据库查询结果 - 所有记录库存充足 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, false); // 两条记录 + + when(resultSet.getString("operationTime")).thenReturn("2024-01-01", "2024-01-02"); + when(resultSet.getString("depart")).thenReturn("手术室", "急诊科"); + when(resultSet.getString("tousseName")).thenReturn("器械包A", "器械包B"); + when(resultSet.getString("operationName")).thenReturn("手术A", "手术B"); + when(resultSet.getInt("amount")).thenReturn(10, 15); // 总需求 = 25 + when(resultSet.getLong("ipId")).thenReturn(1001L, 1002L); + + // Mock 库存充足 + when(objectDao.countBySql(anyString())).thenReturn(20, 25); // 库存都大于需求 + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证总数统计 - 无缺口情况 + assertNotNull(result); + assertEquals(2, result.size()); + + assertEquals(25, parametMap.get("totalTotalAmount")); // 总需求: 10+15=25 + assertEquals(45, parametMap.get("totalStockAmount")); // 总库存: 20+25=45 + assertEquals(0, parametMap.get("totalExpectedGapAmount")); // 总缺口: 0 + + // 验证所有记录都无紧急状态 - 使用VO的getter方法 + for (InstrumentSetDemandReportVo record : result) { + assertEquals(0, record.getExpectedGapAmount()); + assertEquals("否", record.getIsUrgent()); + } + } + + /** + * 测试用例 13: 单条记录的总数统计 + */ + @Test + public void testGetInstrumentSetDemandReportData_SingleRecordTotalCalculation() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 单条记录 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, false); + + when(resultSet.getString("operationTime")).thenReturn("2024-01-01"); + when(resultSet.getString("depart")).thenReturn("手术室"); + when(resultSet.getString("tousseName")).thenReturn("测试器械包"); + when(resultSet.getString("operationName")).thenReturn("测试手术"); + when(resultSet.getInt("amount")).thenReturn(50); + when(resultSet.getLong("ipId")).thenReturn(1001L); + + // Mock 库存 + when(objectDao.countBySql(anyString())).thenReturn(30); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证单条记录的总数统计 + assertNotNull(result); + assertEquals(1, result.size()); + + assertEquals(50, parametMap.get("totalTotalAmount")); // 总需求 = 50 + assertEquals(30, parametMap.get("totalStockAmount")); // 总库存 = 30 + assertEquals(20, parametMap.get("totalExpectedGapAmount")); // 总缺口 = 20 + + InstrumentSetDemandReportVo record = result.get(0); + assertEquals(20, record.getExpectedGapAmount()); + assertEquals("是", record.getIsUrgent()); + } + + /** + * 测试用例 14: 空结果时的总数统计 + */ + @Test + public void testGetInstrumentSetDemandReportData_EmptyResultTotalCalculation() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 空结果 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证空结果时的总数统计 + assertNotNull(result); + assertTrue(result.isEmpty()); + + // 总数应该为0 + assertEquals(0, parametMap.get("totalTotalAmount")); + assertEquals(0, parametMap.get("totalStockAmount")); + assertEquals(0, parametMap.get("totalExpectedGapAmount")); + } + + /** + * 测试用例 15: 边界情况 - 零需求和零库存 + */ + @Test + public void testGetInstrumentSetDemandReportData_ZeroAmountTotalCalculation() throws Exception { + // 准备测试数据 + requestParameters.put("startDate", "2024-01-01"); + requestParameters.put("endDate", "2024-01-31"); + + // Mock 零需求和零库存的记录 + when(objectDao.executeSql(anyString())).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true, true, false); + + when(resultSet.getString("operationTime")).thenReturn("2024-01-01", "2024-01-02"); + when(resultSet.getString("depart")).thenReturn("手术室", "急诊科"); + when(resultSet.getString("tousseName")).thenReturn("器械包A", "器械包B"); + when(resultSet.getString("operationName")).thenReturn("手术A", "手术B"); + when(resultSet.getInt("amount")).thenReturn(0, 10); // 一条零需求记录 + when(resultSet.getLong("ipId")).thenReturn(1001L, 1002L); + + // Mock 库存 - 一条零库存记录 + when(objectDao.countBySql(contains("器械包A"))).thenReturn(5); + when(objectDao.countBySql(contains("器械包B"))).thenReturn(0); + + // 执行测试 + List result = instrumentSetDemandReportHelper.getInstrumentSetDemandReportData(requestParameters, parametMap); + + // 验证边界情况的总数统计 + assertNotNull(result); + assertEquals(2, result.size()); + + assertEquals(10, parametMap.get("totalTotalAmount")); // 总需求: 0+10=10 + assertEquals(5, parametMap.get("totalStockAmount")); // 总库存: 5+0=5 + assertEquals(10, parametMap.get("totalExpectedGapAmount")); // 总缺口: 0+10=10 + + // 验证具体记录 - 使用VO的getter方法 + InstrumentSetDemandReportVo record1 = findRecordByTousseName(result, "器械包A"); + assertEquals(0, record1.getExpectedGapAmount()); // 0-5=-5,取0 + assertEquals("否", record1.getIsUrgent()); + + InstrumentSetDemandReportVo record2 = findRecordByTousseName(result, "器械包B"); + assertEquals(10, record2.getExpectedGapAmount()); // 10-0=10 + assertEquals("是", record2.getIsUrgent()); + } + + /** + * 辅助方法:根据器械包名称查找记录 - 更新为VO类型 + */ + private InstrumentSetDemandReportVo findRecordByTousseName(List result, String tousseName) { + return result.stream() + .filter(record -> tousseName.equals(record.getTousseName())) + .findFirst() + .orElse(null); + } + +}