From 1f889b65bf9b07b691ddc00000f0da22ec44e48c Mon Sep 17 00:00:00 2001 From: lzh Date: Tue, 6 Jan 2026 10:53:37 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E3=80=90ops=E3=80=91fsm=E5=92=8Corder?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=951.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/fsm/OrderStateMachineTest.java | 248 ++++++++++++ .../service/order/OpsOrderServiceTest.java | 359 ++++++++++++++++++ 2 files changed, 607 insertions(+) create mode 100644 viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java create mode 100644 viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java new file mode 100644 index 0000000..030531f --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/fsm/OrderStateMachineTest.java @@ -0,0 +1,248 @@ +package com.viewsh.module.ops.service.fsm; + +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import com.viewsh.module.ops.enums.OperatorTypeEnum; +import com.viewsh.module.ops.enums.PriorityEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import com.viewsh.module.ops.service.event.OpsOrderEventService; +import org.junit.jupiter.api.BeforeEach; +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 static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +/** + * 工单状态机测试 + * + * @author lzh + */ +@ExtendWith(MockitoExtension.class) +public class OrderStateMachineTest { + + @InjectMocks + private OrderStateMachine orderStateMachine; + + @Mock + private OpsOrderMapper opsOrderMapper; + + @Mock + private OpsOrderEventService eventService; + + private OpsOrderDO testOrder; + + @BeforeEach + void setUp() { + // 初始化测试工单 + testOrder = OpsOrderDO.builder() + .id(1L) + .orderCode("WO20250104143025001") + .orderType("CLEAN") + .status(WorkOrderStatusEnum.PENDING.getStatus()) + .title("2楼电梯厅有水渍") + .priority(PriorityEnum.P2.getPriority()) + .build(); + } + + @Test + void testTransition_PendingToAssigned_Success() { + // Given + WorkOrderStatusEnum fromStatus = WorkOrderStatusEnum.PENDING; + WorkOrderStatusEnum toStatus = WorkOrderStatusEnum.DISPATCHED; + + // When + assertDoesNotThrow(() -> { + orderStateMachine.transition( + testOrder, + toStatus, + OperatorTypeEnum.SYSTEM, + null, + "系统自动分配" + ); + }); + + // Then + assertEquals(WorkOrderStatusEnum.DISPATCHED.getStatus(), testOrder.getStatus()); + verify(opsOrderMapper, times(1)).updateById((OpsOrderDO) any()); + verify(eventService, times(1)).recordEvent( + anyLong(), anyString(), anyString(), anyString(), anyString(), any(), anyString() + ); + } + + @Test + void testTransition_AssignedToArrived_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.DISPATCHED.getStatus()); + testOrder.setAssigneeId(1001L); + + // When + assertDoesNotThrow(() -> { + orderStateMachine.transition( + testOrder, + WorkOrderStatusEnum.ARRIVED, + OperatorTypeEnum.CLEANER, + 1001L, + "保洁员接单" + ); + }); + + // Then + assertEquals(WorkOrderStatusEnum.ARRIVED.getStatus(), testOrder.getStatus()); + assertNotNull(testOrder.getStartTime()); + } + + @Test + void testTransition_ArrivedToCompleted_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.ARRIVED.getStatus()); + testOrder.setAssigneeId(1001L); + + // When + assertDoesNotThrow(() -> { + orderStateMachine.transition( + testOrder, + WorkOrderStatusEnum.COMPLETED, + OperatorTypeEnum.CLEANER, + 1001L, + "完成工单" + ); + }); + + // Then + assertEquals(WorkOrderStatusEnum.COMPLETED.getStatus(), testOrder.getStatus()); + assertNotNull(testOrder.getEndTime()); + } + + @Test + void testTransition_InvalidTransition_ThrowsException() { + // Given: 尝试非法转换 PENDING -> COMPLETED + WorkOrderStatusEnum toStatus = WorkOrderStatusEnum.COMPLETED; + + // When & Then + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> { + orderStateMachine.transition( + testOrder, + toStatus, + OperatorTypeEnum.SYSTEM, + null, + "测试非法转换" + ); + }); + + assertTrue(exception.getMessage().contains("非法状态转换")); + } + + @Test + void testTransition_PendingToCancelled_Success() { + // Given + WorkOrderStatusEnum toStatus = WorkOrderStatusEnum.CANCELLED; + + // When + assertDoesNotThrow(() -> { + orderStateMachine.transition( + testOrder, + toStatus, + OperatorTypeEnum.ADMIN, + 1002L, + "取消工单" + ); + }); + + // Then + assertEquals(WorkOrderStatusEnum.CANCELLED.getStatus(), testOrder.getStatus()); + assertNotNull(testOrder.getEndTime()); + } + + @Test + void testTransition_ArrivedToPaused_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.ARRIVED.getStatus()); + testOrder.setAssigneeId(1001L); + + // When + assertDoesNotThrow(() -> { + orderStateMachine.transition( + testOrder, + WorkOrderStatusEnum.PAUSED, + OperatorTypeEnum.CLEANER, + 1001L, + "P0紧急工单插队" + ); + }); + + // Then + assertEquals(WorkOrderStatusEnum.PAUSED.getStatus(), testOrder.getStatus()); + } + + @Test + void testTransition_PausedToArrived_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.PAUSED.getStatus()); + testOrder.setAssigneeId(1001L); + + // When + assertDoesNotThrow(() -> { + orderStateMachine.transition( + testOrder, + WorkOrderStatusEnum.ARRIVED, + OperatorTypeEnum.CLEANER, + 1001L, + "恢复工单" + ); + }); + + // Then + assertEquals(WorkOrderStatusEnum.ARRIVED.getStatus(), testOrder.getStatus()); + } + + @Test + void testTransition_NullOrder_ThrowsException() { + // Given + OpsOrderDO nullOrder = null; + + // When & Then + assertThrows(IllegalArgumentException.class, () -> { + orderStateMachine.transition( + nullOrder, + WorkOrderStatusEnum.DISPATCHED, + OperatorTypeEnum.SYSTEM, + null, + "测试" + ); + }); + } + + @Test + void testGetAllowedTransitions_PendingStatus() { + // Given + WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.PENDING; + + // When + var allowedTransitions = orderStateMachine.getAllowedTransitions(currentStatus); + + // Then + assertNotNull(allowedTransitions); + assertEquals(2, allowedTransitions.size()); + assertTrue(allowedTransitions.contains(WorkOrderStatusEnum.DISPATCHED)); + assertTrue(allowedTransitions.contains(WorkOrderStatusEnum.CANCELLED)); + } + + @Test + void testGetAllowedTransitions_CompletedStatus() { + // Given + WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.COMPLETED; + + // When + var allowedTransitions = orderStateMachine.getAllowedTransitions(currentStatus); + + // Then + assertNotNull(allowedTransitions); + assertTrue(allowedTransitions.isEmpty()); // 终态,无后续转换 + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java new file mode 100644 index 0000000..c003a8e --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/test/java/com/viewsh/module/ops/service/order/OpsOrderServiceTest.java @@ -0,0 +1,359 @@ +package com.viewsh.module.ops.service.order; + +import com.viewsh.framework.common.pojo.PageResult; +import com.viewsh.module.ops.dal.dataobject.dto.*; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; +import com.viewsh.module.ops.enums.OperatorTypeEnum; +import com.viewsh.module.ops.enums.PriorityEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; +import com.viewsh.module.ops.service.fsm.OrderStateMachine; +import org.junit.jupiter.api.BeforeEach; +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 static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +/** + * 工单服务测试 + * + * @author lzh + */ +@ExtendWith(MockitoExtension.class) +public class OpsOrderServiceTest { + + @Mock + private OpsOrderMapper opsOrderMapper; + + @Mock + private OrderStateMachine orderStateMachine; + + @InjectMocks + private OpsOrderServiceImpl opsOrderService; + + private OpsOrderDO testOrder; + + @BeforeEach + void setUp() { + // 初始化测试工单 + testOrder = OpsOrderDO.builder() + .id(1L) + .orderCode("WO20250104143025001") + .orderType("CLEAN") + .status(WorkOrderStatusEnum.PENDING.getStatus()) + .title("2楼电梯厅有水渍") + .priority(PriorityEnum.P2.getPriority()) + .areaId(100L) + .location("2楼电梯厅") + .inspectorId(1001L) + .build(); + } + + @Test + void testCreateOrder_Success() { + // Given + OpsOrderCreateReqDTO createReq = new OpsOrderCreateReqDTO(); + createReq.setOrderType("CLEAN"); + createReq.setSourceType("MANUAL"); + createReq.setTitle("2楼电梯厅有水渍"); + createReq.setDescription("需要紧急处理"); + createReq.setPriority(PriorityEnum.P0.getPriority()); + createReq.setAreaId(100L); + createReq.setLocation("2楼电梯厅"); + createReq.setInspectorId(1001L); + + lenient().when(opsOrderMapper.insert((OpsOrderDO) any())).thenAnswer(invocation -> { + OpsOrderDO order = invocation.getArgument(0); + // 模拟 MyBatis Plus 的 ID 自动回填 + if (order.getId() == null) { + order.setId(1L); + } + return 1; + }); + lenient().doAnswer(invocation -> { + OpsOrderDO order = invocation.getArgument(0); + order.setStatus(WorkOrderStatusEnum.PENDING.getStatus()); + return null; + }).when(orderStateMachine).transition( + any(OpsOrderDO.class), + any(WorkOrderStatusEnum.class), + any(OperatorTypeEnum.class), + anyLong(), + anyString() + ); + + // When + Long orderId = opsOrderService.createOrder(createReq); + + // Then + assertNotNull(orderId); + verify(opsOrderMapper, times(1)).insert((OpsOrderDO) any()); + verify(orderStateMachine, times(1)).transition( + any(OpsOrderDO.class), + eq(WorkOrderStatusEnum.PENDING), + eq(OperatorTypeEnum.SYSTEM), + eq(null), + eq("创建工单") + ); + } + + @Test + void testCreateOrder_WithDefaults() { + // Given: 不指定优先级和状态 + OpsOrderCreateReqDTO createReq = new OpsOrderCreateReqDTO(); + createReq.setOrderType("CLEAN"); + createReq.setTitle("测试工单"); + + when(opsOrderMapper.insert((OpsOrderDO) any())).thenReturn(1); + + // When + opsOrderService.createOrder(createReq); + + // Then: 验证默认值设置 + verify(opsOrderMapper).insert(argThat((OpsOrderDO order) -> + order.getPriority().equals(PriorityEnum.P2.getPriority()) && + order.getStatus().equals(WorkOrderStatusEnum.PENDING.getStatus()) && + order.getSourceType().equals("MANUAL") + )); + } + + @Test + void testUpdateOrder_Success() { + // Given + OpsOrderUpdateReqDTO updateReq = new OpsOrderUpdateReqDTO(); + updateReq.setId(1L); + updateReq.setTitle("更新后的标题"); + updateReq.setDescription("更新后的描述"); + updateReq.setPriority(PriorityEnum.P0.getPriority()); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + when(opsOrderMapper.updateById((OpsOrderDO) any())).thenReturn(1); + + // When + assertDoesNotThrow(() -> opsOrderService.updateOrder(updateReq)); + + // Then + verify(opsOrderMapper, times(1)).updateById((OpsOrderDO) any()); + } + + @Test + void testUpdateOrder_OrderNotFound_ThrowsException() { + // Given + OpsOrderUpdateReqDTO updateReq = new OpsOrderUpdateReqDTO(); + updateReq.setId(999L); + + when(opsOrderMapper.selectById(999L)).thenReturn(null); + + // When & Then + assertThrows(RuntimeException.class, () -> opsOrderService.updateOrder(updateReq)); + } + + @Test + void testDeleteOrder_Success() { + // Given + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + when(opsOrderMapper.deleteById(1L)).thenReturn(1); + + // When + assertDoesNotThrow(() -> opsOrderService.deleteOrder(1L)); + + // Then + verify(opsOrderMapper, times(1)).deleteById(1L); + } + + @Test + void testDeleteOrder_NotPendingStatus_ThrowsException() { + // Given: 工单状态为ARRIVED(不能删除) + testOrder.setStatus(WorkOrderStatusEnum.ARRIVED.getStatus()); + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When & Then + assertThrows(RuntimeException.class, () -> opsOrderService.deleteOrder(1L)); + } + + @Test + void testGetOrder_Success() { + // Given + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + OpsOrderDO order = opsOrderService.getOrder(1L); + + // Then + assertNotNull(order); + assertEquals(1L, order.getId()); + assertEquals("WO20250104143025001", order.getOrderCode()); + } + + @Test + void testGetOrder_NotFound_ThrowsException() { + // Given + when(opsOrderMapper.selectById(999L)).thenReturn(null); + + // When & Then + assertThrows(RuntimeException.class, () -> opsOrderService.getOrder(999L)); + } + + @Test + void testAssignOrder_Success() { + // Given + OpsOrderAssignReqDTO assignReq = new OpsOrderAssignReqDTO(); + assignReq.setOrderId(1L); + assignReq.setAssigneeId(2001L); + assignReq.setRemark("张师傅负责该区域"); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.assignOrder(assignReq, OperatorTypeEnum.ADMIN, 1002L)); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.PENDING), + eq(OperatorTypeEnum.ADMIN), + eq(1002L), + eq("张师傅负责该区域") + ); + assertEquals(2001L, testOrder.getAssigneeId()); + } + + @Test + void testAcceptOrder_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.PENDING.getStatus()); + testOrder.setAssigneeId(2001L); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.acceptOrder(1L, 2001L)); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.ARRIVED), + eq(OperatorTypeEnum.CLEANER), + eq(2001L), + eq("保洁员接单") + ); + } + + @Test + void testAcceptOrder_WrongAssignee_ThrowsException() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.PENDING.getStatus()); + testOrder.setAssigneeId(2001L); // 分配给张师傅 + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When & Then: 李师傅尝试接单 + assertThrows(RuntimeException.class, () -> opsOrderService.acceptOrder(1L, 2002L)); + } + + @Test + void testCompleteOrder_Success() { + // Given + OpsOrderCompleteReqDTO completeReq = new OpsOrderCompleteReqDTO(); + completeReq.setOrderId(1L); + completeReq.setRemark("已完成清洁"); + + testOrder.setStatus(WorkOrderStatusEnum.ARRIVED.getStatus()); + testOrder.setAssigneeId(2001L); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.completeOrder(completeReq, 2001L)); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.COMPLETED), + eq(OperatorTypeEnum.CLEANER), + eq(2001L), + eq("已完成清洁") + ); + } + + @Test + void testPauseOrder_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.ARRIVED.getStatus()); + testOrder.setAssigneeId(2001L); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.pauseOrder(1L, 2001L, "P0紧急工单插队")); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.PAUSED), + eq(OperatorTypeEnum.CLEANER), + eq(2001L), + eq("P0紧急工单插队") + ); + } + + @Test + void testResumeOrder_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.PAUSED.getStatus()); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.resumeOrder(1L, 2001L)); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.ARRIVED), + eq(OperatorTypeEnum.CLEANER), + eq(2001L), + eq("恢复工单") + ); + } + + @Test + void testCancelOrder_Success() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.PENDING.getStatus()); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When + assertDoesNotThrow(() -> opsOrderService.cancelOrder(1L, "测试取消", OperatorTypeEnum.ADMIN, 1002L)); + + // Then + verify(orderStateMachine, times(1)).transition( + eq(testOrder), + eq(WorkOrderStatusEnum.CANCELLED), + eq(OperatorTypeEnum.ADMIN), + eq(1002L), + eq("测试取消") + ); + } + + @Test + void testCancelOrder_CompletedOrder_ThrowsException() { + // Given + testOrder.setStatus(WorkOrderStatusEnum.COMPLETED.getStatus()); + + when(opsOrderMapper.selectById(1L)).thenReturn(testOrder); + + // When & Then + assertThrows(RuntimeException.class, + () -> opsOrderService.cancelOrder(1L, "测试取消", OperatorTypeEnum.ADMIN, 1002L)); + } + +}