From 5825739b92758fe5ff0149720e6a20d79fd4a31b Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Fri, 30 Oct 2026 18:46:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ExceptionForceCloseTest.java | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionForceCloseTest.java diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionForceCloseTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionForceCloseTest.java new file mode 100644 index 000000000..5e5776895 --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionForceCloseTest.java @@ -0,0 +1,81 @@ +package com.baiyue.lastleg.exceptionorder.service; + +import com.baiyue.lastleg.exceptionorder.dto.ExceptionOrderRequests; +import com.baiyue.lastleg.exceptionorder.entity.ExceptionOrder; +import com.baiyue.lastleg.exceptionorder.entity.ExceptionOrderCommand; +import com.baiyue.lastleg.exceptionorder.repository.ExceptionWorkflowRepository; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.mockito.ArgumentCaptor; +import org.springframework.test.util.ReflectionTestUtils; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** 人工完结只关闭异常,不调用正常实收、库存或运输完成路径。 */ +class ExceptionForceCloseTest { + private final ExceptionWorkflowRepository repository = mock(ExceptionWorkflowRepository.class); + private final ExceptionOrderService service = new ExceptionOrderService(); + /** 仅接入工单仓储边界,其他业务依赖不装配以防误调用。 */ + ExceptionForceCloseTest() { + ReflectionTestUtils.setField(service, "workflow", repository); + ReflectionTestUtils.setField(service, "objectMapper", new ObjectMapper().findAndRegisterModules()); + } + /** 构造已有关联退回及历史实收的工单,历史事实必须保留。 */ + private ExceptionOrder order(String type, String status) { + var order = new ExceptionOrder(); + order.setId(1L); order.setCustomerId("C1"); order.setExceptionNo("EX1"); order.setVersion(3L); + order.setStatus(status); order.setExceptionType(type); order.setReturnDirection("OVERSEAS_WAREHOUSE"); + order.setInventoryAdjustmentId(99L); order.setEvidenceFileIdsJson("[123]"); order.setDeliveryNo("DL1"); + return order; + } + /** 各类型未结束异常无需先开始处理或上传文件,且只有工单及幂等记录写入。 */ + @ParameterizedTest + @CsvSource({"RETURN,PENDING", "RETURN,PROCESSING", "REJECT,PENDING", "REJECT,PROCESSING", "DAMAGED,PENDING", "DAMAGED,PROCESSING", "OTHER,PENDING", "OTHER,PROCESSING"}) + void closesEveryActiveTypeWithoutChangingPhysicalFacts(String type, String status) { + var order = order(type, status); when(repository.lock("C1", "EX1")).thenReturn(order); + assertThat(ReflectionTestUtils.>invokeMethod(service, "allowedActions", order)).contains("FORCE_CLOSE"); + clearInvocations(repository); + var result = service.forceClose("C1", "OP", "EX1", new ExceptionOrderRequests.ForceClose(3, "key", " 客户协商后人工结束 ")); + assertThat(result.status()).isEqualTo("COMPLETED"); assertThat(result.version()).isEqualTo(4); + assertThat(result.restoredPackages()).isZero(); assertThat(order.getCompletedAt()).isNotNull(); + assertThat(order.getHandlingAction()).isEqualTo("其他"); assertThat(order.getHandlingResult()).isEqualTo("客户协商后人工结束"); + assertThat(order.getHandlerId()).isEqualTo("OP"); assertThat(order.getInventoryAdjustmentId()).isEqualTo(99L); + assertThat(order.getEvidenceFileIdsJson()).isEqualTo("[123]"); + verify(repository).updateException(order); + var saved = ArgumentCaptor.forClass(ExceptionOrderCommand.class); verify(repository).insertCommand(saved.capture()); + assertThat(saved.getValue().getAction()).isEqualTo("FORCE_CLOSE"); + verify(repository).lock("C1", "EX1"); verify(repository).closeCommand("C1", "key"); verifyNoMoreInteractions(repository); + } + /** 空白原因直接拒绝,不读取或修改任何工单。 */ + @Test void requiresNonBlankReason() { + assertThatThrownBy(() -> service.forceClose("C1", "OP", "EX1", new ExceptionOrderRequests.ForceClose(3, "key", " "))).hasMessageContaining("填写完结原因"); + verifyNoInteractions(repository); + } + /** 陈旧版本、已结束及其他客户的异常不能被重新完结。 */ + @Test void rejectsStaleTerminalAndForeignOrders() { + var order = order("RETURN", "PROCESSING"); when(repository.lock("C1", "EX1")).thenReturn(order); + assertThatThrownBy(() -> service.forceClose("C1", "OP", "EX1", new ExceptionOrderRequests.ForceClose(2, "key", "原因"))).hasMessageContaining("版本"); + for (String status : java.util.List.of("COMPLETED", "CANCELLED")) { + order.setStatus(status); + assertThat(ReflectionTestUtils.>invokeMethod(service, "allowedActions", order)).doesNotContain("FORCE_CLOSE"); + assertThatThrownBy(() -> service.forceClose("C1", "OP", "EX1", new ExceptionOrderRequests.ForceClose(3, "key", "原因"))).hasMessageContaining("状态"); + } + assertThatThrownBy(() -> service.forceClose("C2", "OP", "EX1", new ExceptionOrderRequests.ForceClose(3, "key", "原因"))).hasMessageContaining("不存在"); + verify(repository, never()).updateException(any()); verify(repository, never()).insertCommand(any()); + } + /** 相同请求恢复原结果,不同原因或其他工单复用同键必须冲突。 */ + @Test void replaysOnceAndRejectsChangedIntent() { + var order = order("RETURN", "PROCESSING"); when(repository.lock("C1", "EX1")).thenReturn(order); + var request = new ExceptionOrderRequests.ForceClose(3, "key", "原因"); + service.forceClose("C1", "OP", "EX1", request); + var saved = ArgumentCaptor.forClass(ExceptionOrderCommand.class); verify(repository).insertCommand(saved.capture()); + when(repository.closeCommand("C1", "key")).thenReturn(saved.getValue()); + assertThat(service.forceClose("C1", "OP", "EX1", request).replayed()).isTrue(); + assertThatThrownBy(() -> service.forceClose("C1", "OP", "EX1", new ExceptionOrderRequests.ForceClose(3, "key", "其他原因"))).hasMessageContaining("幂等"); + var other = order("OTHER", "PENDING"); other.setId(2L); when(repository.lock("C1", "EX2")).thenReturn(other); + assertThatThrownBy(() -> service.forceClose("C1", "OP", "EX2", request)).hasMessageContaining("其他异常单"); + verify(repository, times(1)).updateException(any()); verify(repository, times(1)).insertCommand(any()); + } +}