提交
This commit is contained in:
+81
@@ -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.<java.util.List<String>>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.<java.util.List<String>>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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user