feat(wm-dispatch): #70 应急推演(爆管模拟+水质异常+演练管理)

- 爆管模拟: 影响范围/用户/水量损失/修复时间/关阀方案
- 水质异常: 事件上报/严重度评估/预案匹配/响应流程/处置归档
- 应急演练: 计划创建/执行/完成/评估打分
- 4个Entity + 4个Mapper + 3个Service + 1个Controller(15端点)
- DDL: 4张表 + 4个索引
- 单元测试: 3个测试类
This commit is contained in:
2026-06-14 15:38:22 +08:00
parent 4a0fc1bf42
commit a26a626d21
20 changed files with 1594 additions and 0 deletions
@@ -0,0 +1,253 @@
package com.water.dispatch.service;
import com.water.common.core.exception.BusinessException;
import com.water.dispatch.entity.DrillEvaluation;
import com.water.dispatch.entity.EmergencyDrill;
import com.water.dispatch.entity.dto.DrillCreateRequest;
import com.water.dispatch.entity.dto.DrillEvaluationRequest;
import com.water.dispatch.mapper.DrillEvaluationMapper;
import com.water.dispatch.mapper.EmergencyDrillMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class EmergencyDrillServiceTest {
@Mock
private EmergencyDrillMapper drillMapper;
@Mock
private DrillEvaluationMapper evaluationMapper;
@InjectMocks
private EmergencyDrillService emergencyDrillService;
@Test
void testCreateDrill() {
when(drillMapper.insert(any())).thenAnswer(invocation -> {
EmergencyDrill drill = invocation.getArgument(0);
drill.setId(1L);
return 1;
});
DrillCreateRequest request = new DrillCreateRequest();
request.setName("2024年度爆管抢修演练");
request.setDrillType("PIPE_BURST");
request.setScenario("模拟DN600主干管爆裂");
request.setObjectives("检验应急响应速度和关阀操作");
request.setOrganizerId(1L);
request.setOrganizerName("张三");
request.setLocation("城南水厂");
request.setPlannedDate(LocalDate.of(2024, 6, 15));
request.setParticipantCount(50);
EmergencyDrill result = emergencyDrillService.createDrill(request);
assertNotNull(result.getDrillNo());
assertTrue(result.getDrillNo().startsWith("DRILL-"));
assertEquals("PLANNED", result.getStatus());
assertEquals("PIPE_BURST", result.getDrillType());
assertEquals("2024年度爆管抢修演练", result.getName());
verify(drillMapper).insert(any());
}
@Test
void testCreateDrillMissingName() {
DrillCreateRequest request = new DrillCreateRequest();
assertThrows(BusinessException.class, () -> emergencyDrillService.createDrill(request));
}
@Test
void testStartDrill() {
EmergencyDrill drill = buildDrill(1L, "PLANNED");
when(drillMapper.selectById(1L)).thenReturn(drill);
when(drillMapper.updateById(any())).thenReturn(1);
EmergencyDrill result = emergencyDrillService.startDrill(1L);
assertEquals("IN_PROGRESS", result.getStatus());
assertNotNull(result.getActualStartTime());
assertNotNull(result.getExecutionLog());
}
@Test
void testStartDrillWrongStatus() {
EmergencyDrill drill = buildDrill(1L, "IN_PROGRESS");
when(drillMapper.selectById(1L)).thenReturn(drill);
assertThrows(BusinessException.class, () -> emergencyDrillService.startDrill(1L));
}
@Test
void testLogExecution() {
EmergencyDrill drill = buildDrill(1L, "IN_PROGRESS");
drill.setExecutionLog("[]");
when(drillMapper.selectById(1L)).thenReturn(drill);
when(drillMapper.updateById(any())).thenReturn(1);
EmergencyDrill result = emergencyDrillService.logExecution(1L, "关阀操作", "完成上游阀门关闭", "李四");
assertEquals("IN_PROGRESS", result.getStatus());
assertNotNull(result.getExecutionLog());
}
@Test
void testLogExecutionWrongStatus() {
EmergencyDrill drill = buildDrill(1L, "PLANNED");
when(drillMapper.selectById(1L)).thenReturn(drill);
assertThrows(BusinessException.class,
() -> emergencyDrillService.logExecution(1L, "stage", "content", "operator"));
}
@Test
void testCompleteDrill() {
EmergencyDrill drill = buildDrill(1L, "IN_PROGRESS");
drill.setExecutionLog("[]");
when(drillMapper.selectById(1L)).thenReturn(drill);
when(drillMapper.updateById(any())).thenReturn(1);
EmergencyDrill result = emergencyDrillService.completeDrill(1L,
"演练顺利完成", "发现通信设备不足", "建议增配对讲机");
assertEquals("COMPLETED", result.getStatus());
assertNotNull(result.getActualEndTime());
assertEquals("演练顺利完成", result.getSummary());
assertEquals("发现通信设备不足", result.getIssuesFound());
}
@Test
void testCompleteDrillWrongStatus() {
EmergencyDrill drill = buildDrill(1L, "PLANNED");
when(drillMapper.selectById(1L)).thenReturn(drill);
assertThrows(BusinessException.class,
() -> emergencyDrillService.completeDrill(1L, "summary", "issues", "improvements"));
}
@Test
void testCancelDrill() {
EmergencyDrill drill = buildDrill(1L, "PLANNED");
when(drillMapper.selectById(1L)).thenReturn(drill);
when(drillMapper.updateById(any())).thenReturn(1);
EmergencyDrill result = emergencyDrillService.cancelDrill(1L, "天气原因取消");
assertEquals("CANCELLED", result.getStatus());
}
@Test
void testCancelCompletedDrill() {
EmergencyDrill drill = buildDrill(1L, "COMPLETED");
when(drillMapper.selectById(1L)).thenReturn(drill);
assertThrows(BusinessException.class, () -> emergencyDrillService.cancelDrill(1L, "reason"));
}
@Test
void testEvaluateDrill() {
EmergencyDrill drill = buildDrill(1L, "COMPLETED");
when(drillMapper.selectById(1L)).thenReturn(drill);
when(drillMapper.updateById(any())).thenReturn(1);
when(evaluationMapper.insert(any())).thenAnswer(invocation -> {
DrillEvaluation eval = invocation.getArgument(0);
eval.setId(1L);
return 1;
});
DrillEvaluationRequest request = new DrillEvaluationRequest();
request.setDrillId(1L);
request.setEvaluatorId(1L);
request.setEvaluatorName("王五");
request.setResponseScore(85);
request.setHandlingScore(90);
request.setCoordinationScore(80);
request.setResourceScore(75);
request.setReportingScore(88);
request.setStrengths("响应迅速");
request.setWeaknesses("资源调配待加强");
request.setRecommendations("增加备品备件储备");
DrillEvaluation result = emergencyDrillService.evaluate(request);
assertNotNull(result.getEvaluationNo());
assertTrue(result.getEvaluationNo().startsWith("EVAL-"));
assertNotNull(result.getOverallScore());
assertTrue(result.getOverallScore() > 0);
assertNotNull(result.getGrade());
assertEquals("SUBMITTED", result.getStatus());
// 验证综合评分计算
// 85*0.25 + 90*0.30 + 80*0.20 + 75*0.15 + 88*0.10 = 21.25+27+16+11.25+8.8 = 84.3 ≈ 84
assertEquals(84, result.getOverallScore());
assertEquals("GOOD", result.getGrade());
verify(drillMapper).updateById(any()); // 更新演练状态为 EVALUATED
}
@Test
void testEvaluateWrongStatus() {
EmergencyDrill drill = buildDrill(1L, "PLANNED");
when(drillMapper.selectById(1L)).thenReturn(drill);
DrillEvaluationRequest request = new DrillEvaluationRequest();
request.setDrillId(1L);
assertThrows(BusinessException.class, () -> emergencyDrillService.evaluate(request));
}
@Test
void testGetDrillStatistics() {
when(drillMapper.selectCount(any())).thenReturn(10L, 5L, 3L, 2L);
when(evaluationMapper.selectList(any())).thenReturn(Collections.emptyList());
Map<String, Object> stats = emergencyDrillService.getDrillStatistics();
assertNotNull(stats);
assertEquals(10L, stats.get("totalDrills"));
assertEquals(5L, stats.get("completedDrills"));
assertEquals(3L, stats.get("evaluatedDrills"));
assertEquals(2L, stats.get("plannedDrills"));
}
@Test
void testGetDrillStatisticsWithEvaluations() {
when(drillMapper.selectCount(any())).thenReturn(5L, 3L, 2L, 0L);
DrillEvaluation eval1 = new DrillEvaluation();
eval1.setOverallScore(85);
DrillEvaluation eval2 = new DrillEvaluation();
eval2.setOverallScore(75);
when(evaluationMapper.selectList(any())).thenReturn(List.of(eval1, eval2));
Map<String, Object> stats = emergencyDrillService.getDrillStatistics();
assertEquals(80.0, stats.get("averageScore"));
}
private EmergencyDrill buildDrill(Long id, String status) {
EmergencyDrill drill = new EmergencyDrill();
drill.setId(id);
drill.setDrillNo("DRILL-TEST-001");
drill.setName("测试演练");
drill.setDrillType("PIPE_BURST");
drill.setScenario("测试场景");
drill.setStatus(status);
drill.setOrganizerId(1L);
drill.setOrganizerName("张三");
return drill;
}
}
@@ -0,0 +1,164 @@
package com.water.dispatch.service;
import com.water.common.core.exception.BusinessException;
import com.water.dispatch.entity.PipeBurstSimulation;
import com.water.dispatch.entity.dto.PipeBurstRequest;
import com.water.dispatch.mapper.PipeBurstSimulationMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class PipeBurstServiceTest {
@Mock
private PipeBurstSimulationMapper simulationMapper;
@InjectMocks
private PipeBurstService pipeBurstService;
@Test
void testSimulatePipeBurst() {
when(simulationMapper.insert(any())).thenAnswer(invocation -> {
PipeBurstSimulation sim = invocation.getArgument(0);
sim.setId(1L);
return 1;
});
when(simulationMapper.updateById(any())).thenReturn(1);
PipeBurstRequest request = new PipeBurstRequest();
request.setLongitude(116.404);
request.setLatitude(39.915);
request.setLocation("北京市朝阳区建国路100号");
request.setPipeDiameter(400.0);
request.setPipeMaterial("球墨铸铁");
request.setPipePressure(0.35);
request.setCreatorId(1L);
request.setCreatorName("张三");
PipeBurstSimulation result = pipeBurstService.simulate(request);
assertNotNull(result.getSimulationNo());
assertTrue(result.getSimulationNo().startsWith("PBS-"));
assertEquals("COMPLETED", result.getStatus());
assertNotNull(result.getImpactRadius());
assertTrue(result.getImpactRadius() > 0);
assertNotNull(result.getAffectedUsers());
assertTrue(result.getAffectedUsers() > 0);
assertNotNull(result.getEstimatedRepairHours());
assertTrue(result.getEstimatedRepairHours() > 0);
assertNotNull(result.getValveShutdownPlan());
assertNotNull(result.getLeakageRate());
verify(simulationMapper).insert(any());
verify(simulationMapper, atLeastOnce()).updateById(any());
}
@Test
void testSimulateMissingCoordinates() {
PipeBurstRequest request = new PipeBurstRequest();
request.setLocation("测试位置");
assertThrows(BusinessException.class, () -> pipeBurstService.simulate(request));
}
@Test
void testSimulateDefaultValues() {
when(simulationMapper.insert(any())).thenReturn(1);
when(simulationMapper.updateById(any())).thenReturn(1);
PipeBurstRequest request = new PipeBurstRequest();
request.setLongitude(120.0);
request.setLatitude(30.0);
// 不设置pipeDiameter和pipePressure,使用默认值
PipeBurstSimulation result = pipeBurstService.simulate(request);
assertNotNull(result);
assertEquals("COMPLETED", result.getStatus());
assertNotNull(result.getImpactRadius());
}
@Test
void testGetImpactAnalysis() {
PipeBurstSimulation sim = buildSimulation(1L, "COMPLETED");
when(simulationMapper.selectById(1L)).thenReturn(sim);
Map<String, Object> analysis = pipeBurstService.getImpactAnalysis(1L);
assertNotNull(analysis);
assertEquals("PBS-TEST-001", analysis.get("simulationNo"));
assertTrue(analysis.containsKey("impactRadius"));
assertTrue(analysis.containsKey("affectedUsers"));
assertTrue(analysis.containsKey("valveShutdownPlan"));
}
@Test
void testGetValvePlan() {
PipeBurstSimulation sim = buildSimulation(1L, "COMPLETED");
when(simulationMapper.selectById(1L)).thenReturn(sim);
Map<String, Object> plan = pipeBurstService.getValvePlan(1L);
assertNotNull(plan);
assertEquals("PBS-TEST-001", plan.get("pipeNo"));
assertTrue(plan.containsKey("valveShutdownPlan"));
assertTrue(plan.containsKey("recommendation"));
}
@Test
void testArchiveSimulation() {
PipeBurstSimulation sim = buildSimulation(1L, "COMPLETED");
when(simulationMapper.selectById(1L)).thenReturn(sim);
when(simulationMapper.updateById(any())).thenReturn(1);
PipeBurstSimulation result = pipeBurstService.archive(1L);
assertEquals("ARCHIVED", result.getStatus());
verify(simulationMapper).updateById(any());
}
@Test
void testArchiveNonCompletedSimulation() {
PipeBurstSimulation sim = buildSimulation(1L, "RUNNING");
when(simulationMapper.selectById(1L)).thenReturn(sim);
assertThrows(BusinessException.class, () -> pipeBurstService.archive(1L));
}
@Test
void testGetByIdNotFound() {
when(simulationMapper.selectById(999L)).thenReturn(null);
assertThrows(BusinessException.class, () -> pipeBurstService.getById(999L));
}
private PipeBurstSimulation buildSimulation(Long id, String status) {
PipeBurstSimulation sim = new PipeBurstSimulation();
sim.setId(id);
sim.setSimulationNo("PBS-TEST-001");
sim.setPipeNo("PIPE-001");
sim.setLongitude(116.404);
sim.setLatitude(39.915);
sim.setLocation("测试位置");
sim.setPipeDiameter(300.0);
sim.setPipePressure(0.3);
sim.setImpactRadius(150.0);
sim.setImpactArea(70685.83);
sim.setAffectedUsers(354);
sim.setAffectedRegion("测试区域");
sim.setLeakageRate(100.0);
sim.setEstimatedRepairHours(6.0);
sim.setValveShutdownPlan("[{valveId: V-001}]");
sim.setValveAffectedUsers(637);
sim.setStatus(status);
return sim;
}
}
@@ -0,0 +1,186 @@
package com.water.dispatch.service;
import com.water.common.core.exception.BusinessException;
import com.water.dispatch.entity.WaterQualityIncident;
import com.water.dispatch.entity.dto.WaterQualityRequest;
import com.water.dispatch.mapper.EmergencyPlanMapper;
import com.water.dispatch.mapper.WaterQualityIncidentMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@ExtendWith(MockitoExtension.class)
class WaterQualityServiceTest {
@Mock
private WaterQualityIncidentMapper incidentMapper;
@Mock
private EmergencyPlanMapper planMapper;
@InjectMocks
private WaterQualityService waterQualityService;
@Test
void testReportIncident() {
when(incidentMapper.insert(any())).thenAnswer(invocation -> {
WaterQualityIncident inc = invocation.getArgument(0);
inc.setId(1L);
return 1;
});
when(incidentMapper.updateById(any())).thenReturn(1);
when(planMapper.selectList(any())).thenReturn(Collections.emptyList());
WaterQualityRequest request = new WaterQualityRequest();
request.setTitle("末梢水浊度超标");
request.setDescription("某小区末梢水浊度异常");
request.setSourceType("END");
request.setAbnormalIndicator("TURBIDITY");
request.setDetectedValue(3.5);
request.setStandardValue(1.0);
request.setDetectedTime(LocalDateTime.now());
request.setCreatorId(1L);
request.setCreatorName("张三");
WaterQualityIncident result = waterQualityService.report(request);
assertNotNull(result.getIncidentNo());
assertTrue(result.getIncidentNo().startsWith("WQI-"));
assertEquals("DETECTED", result.getStatus());
assertEquals(3.5, result.getExceedMultiple());
assertNotNull(result.getSeverityLevel());
assertNotNull(result.getWarningMessage());
verify(incidentMapper).insert(any());
}
@Test
void testReportMissingIndicator() {
WaterQualityRequest request = new WaterQualityRequest();
request.setTitle("测试");
assertThrows(BusinessException.class, () -> waterQualityService.report(request));
}
@Test
void testConfirmIncident() {
WaterQualityIncident incident = buildIncident(1L, "DETECTED");
when(incidentMapper.selectById(1L)).thenReturn(incident);
when(incidentMapper.updateById(any())).thenReturn(1);
WaterQualityIncident result = waterQualityService.confirm(1L);
assertEquals("CONFIRMED", result.getStatus());
assertNotNull(result.getConfirmedTime());
}
@Test
void testConfirmWrongStatus() {
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
when(incidentMapper.selectById(1L)).thenReturn(incident);
assertThrows(BusinessException.class, () -> waterQualityService.confirm(1L));
}
@Test
void testStartHandling() {
WaterQualityIncident incident = buildIncident(1L, "CONFIRMED");
when(incidentMapper.selectById(1L)).thenReturn(incident);
when(incidentMapper.updateById(any())).thenReturn(1);
when(planMapper.selectList(any())).thenReturn(Collections.emptyList());
WaterQualityIncident result = waterQualityService.startHandling(1L, 2L, "李四");
assertEquals("HANDLING", result.getStatus());
assertEquals(2L, result.getHandlerId());
assertEquals("李四", result.getHandlerName());
assertNotNull(result.getHandlingStartTime());
assertNotNull(result.getHandlingMeasures());
assertEquals(10, result.getHandlingProgress());
}
@Test
void testUpdateProgress() {
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
when(incidentMapper.selectById(1L)).thenReturn(incident);
when(incidentMapper.updateById(any())).thenReturn(1);
WaterQualityIncident result = waterQualityService.updateProgress(1L, 50, "管网冲洗完成");
assertEquals(50, result.getHandlingProgress());
assertEquals("HANDLING", result.getStatus());
}
@Test
void testUpdateProgressToComplete() {
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
when(incidentMapper.selectById(1L)).thenReturn(incident);
when(incidentMapper.updateById(any())).thenReturn(1);
WaterQualityIncident result = waterQualityService.updateProgress(1L, 100, "全部完成");
assertEquals(100, result.getHandlingProgress());
assertEquals("RESOLVED", result.getStatus());
assertNotNull(result.getResolvedTime());
}
@Test
void testResolveIncident() {
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
when(incidentMapper.selectById(1L)).thenReturn(incident);
when(incidentMapper.updateById(any())).thenReturn(1);
WaterQualityIncident result = waterQualityService.resolve(1L, "水质恢复正常");
assertEquals("RESOLVED", result.getStatus());
assertNotNull(result.getResolvedTime());
}
@Test
void testResolveAlreadyResolved() {
WaterQualityIncident incident = buildIncident(1L, "RESOLVED");
when(incidentMapper.selectById(1L)).thenReturn(incident);
assertThrows(BusinessException.class, () -> waterQualityService.resolve(1L, "test"));
}
@Test
void testGetHandlingTimeline() {
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
incident.setDetectedTime(LocalDateTime.of(2024, 1, 1, 8, 0));
incident.setConfirmedTime(LocalDateTime.of(2024, 1, 1, 9, 0));
incident.setHandlingStartTime(LocalDateTime.of(2024, 1, 1, 10, 0));
when(incidentMapper.selectById(1L)).thenReturn(incident);
Map<String, Object> timeline = waterQualityService.getHandlingTimeline(1L);
assertNotNull(timeline);
assertEquals("WQI-TEST-001", timeline.get("incidentNo"));
assertEquals("HANDLING", timeline.get("status"));
assertNotNull(timeline.get("timeline"));
}
private WaterQualityIncident buildIncident(Long id, String status) {
WaterQualityIncident incident = new WaterQualityIncident();
incident.setId(id);
incident.setIncidentNo("WQI-TEST-001");
incident.setTitle("浊度超标");
incident.setAbnormalIndicator("TURBIDITY");
incident.setDetectedValue(3.5);
incident.setStandardValue(1.0);
incident.setExceedMultiple(3.5);
incident.setSeverityLevel("LEVEL_2");
incident.setStatus(status);
incident.setHandlingProgress(0);
return incident;
}
}