This commit is contained in:
2026-08-08 14:17:06 +08:00
parent 87508bba82
commit 87c1bfbb71
9 changed files with 267 additions and 11 deletions
@@ -0,0 +1,24 @@
package com.baiyue.firstleg.repository;
import com.baiyue.firstleg.entity.TruckAppointmentEntity;
import com.baiyue.firstleg.mapper.TruckAppointmentMapper;
import com.baomidou.mybatisplus.annotation.TableName;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class TruckAppointmentMapperContractTest {
@Test void mapsToPhase3TableAndUsesOptimisticGuards() throws Exception {
assertThat(TruckAppointmentEntity.class.getAnnotation(TableName.class).value()).isEqualTo("fl_truck_appointment");
String page = TruckAppointmentMapper.class.getMethod("page", com.baomidou.mybatisplus.extension.plugins.pagination.Page.class, com.baiyue.firstleg.dto.TruckAppointmentDtos.Query.class).getAnnotation(Select.class).value()[0];
assertThat(page).contains("fl_truck_appointment", "appointment_no", "vehicle_no", "status=#{q.status}", "container_booking_id=#{q.containerBookingId}");
String transition = TruckAppointmentMapper.class.getMethod("transition", long.class, String.class, String.class, int.class).getAnnotation(Update.class).value();
assertThat(transition).contains("status=#{toStatus}", "status=#{fromStatus}", "version=#{version}", "revision=revision+1", "version=version+1");
String reschedule = TruckAppointmentMapper.class.getMethod("reschedule", long.class, java.time.LocalDateTime.class, String.class, int.class).getAnnotation(Update.class).value();
assertThat(reschedule).contains("status='RESCHEDULED'", "version=#{version}", "revision=revision+1");
String edit = TruckAppointmentMapper.class.getMethod("updateEdit", TruckAppointmentEntity.class, int.class).getAnnotation(Update.class).value();
assertThat(edit).contains("WHERE id=#{e.id} AND version=#{version}", "version=version+1", "revision=revision+1");
}
}
@@ -0,0 +1,90 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.domain.TruckAppointmentStatus;
import com.baiyue.firstleg.dto.TruckAppointmentDtos.ActionRequest;
import com.baiyue.firstleg.dto.TruckAppointmentDtos.SaveRequest;
import com.baiyue.firstleg.entity.TruckAppointmentEntity;
import com.baiyue.firstleg.entity.TruckAppointmentHistoryEntity;
import com.baiyue.firstleg.entity.TruckAppointmentOperationEntity;
import com.baiyue.firstleg.repository.TruckAppointmentRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.lang.reflect.Constructor;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
class TruckAppointmentServiceTest {
@Mock TruckAppointmentRepository repository;
@InjectMocks TruckAppointmentService service;
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this); }
@Test void createRequiresZeroVersionAndInitialisesDraft() {
SaveRequest invalid = request(1);
assertThatThrownBy(() -> service.create(7L, "admin", invalid)).isInstanceOf(BizException.class);
verifyNoInteractions(repository);
}
@Test void actionAdvancesMainLifecycleAndWritesIdempotentOperation() {
TruckAppointmentEntity e = entity(TruckAppointmentStatus.DRAFT, 0);
when(repository.find(1L)).thenReturn(e);
when(repository.transition(1L, "DRAFT", "PENDING_DISPATCH", 0)).thenReturn(1);
var result = service.action(1L, "submit", new ActionRequest(0, "提交"), "idem-1", "hash-1", 7L, "admin");
assertThat(result.getStatus()).isEqualTo(TruckAppointmentStatus.PENDING_DISPATCH);
assertThat(result.getVersion()).isEqualTo(1);
assertThat(result.getRevision()).isEqualTo(1);
ArgumentCaptor<TruckAppointmentOperationEntity> operation = ArgumentCaptor.forClass(TruckAppointmentOperationEntity.class);
verify(repository).operation(operation.capture());
assertThat(operation.getValue().getIdempotencyKey()).isEqualTo("idem-1");
verify(repository).history(any(TruckAppointmentHistoryEntity.class));
}
@Test void rejectsRescheduleThroughGenericActionAndCompletedReschedule() {
TruckAppointmentEntity e = entity(TruckAppointmentStatus.DRAFT, 0);
when(repository.find(1L)).thenReturn(e);
assertThatThrownBy(() -> service.action(1L, "RESCHEDULE", new ActionRequest(0, null), null, null, 7L, "admin"))
.isInstanceOf(BizException.class).hasMessageContaining("专用接口");
e.setStatus(TruckAppointmentStatus.COMPLETED);
assertThatThrownBy(() -> service.reschedule(1L, LocalDateTime.now(), new ActionRequest(0, "改约"), 7L, "admin"))
.isInstanceOf(BizException.class).hasMessageContaining("不可改约");
verify(repository, never()).reschedule(anyLong(), any(), anyString(), anyInt());
}
@Test void rescheduleKeepsFromStatusAndIncrementsOptimisticFields() {
TruckAppointmentEntity e = entity(TruckAppointmentStatus.DISPATCHED, 2);
when(repository.find(1L)).thenReturn(e);
when(repository.reschedule(eq(1L), any(), eq("改约"), eq(2))).thenReturn(1);
var at = LocalDateTime.of(2026, 8, 12, 10, 0);
var result = service.reschedule(1L, at, new ActionRequest(2, "改约"), 7L, "admin");
assertThat(result.getStatus()).isEqualTo(TruckAppointmentStatus.RESCHEDULED);
assertThat(result.getVersion()).isEqualTo(3);
ArgumentCaptor<TruckAppointmentHistoryEntity> history = ArgumentCaptor.forClass(TruckAppointmentHistoryEntity.class);
verify(repository).history(history.capture());
assertThat(history.getValue().getFromStatus()).isEqualTo("DISPATCHED");
assertThat(history.getValue().getToStatus()).isEqualTo("RESCHEDULED");
}
private static TruckAppointmentEntity entity(TruckAppointmentStatus status, int version) {
TruckAppointmentEntity e = new TruckAppointmentEntity(); e.setId(1L); e.setStatus(status); e.setVersion(version); e.setRevision(version); return e;
}
private static SaveRequest request(int expectedVersion) {
try {
Constructor<?> c = SaveRequest.class.getDeclaredConstructors()[0];
Object[] args = new Object[c.getParameterCount()]; args[args.length - 1] = expectedVersion;
return (SaveRequest) c.newInstance(args);
} catch (ReflectiveOperationException ex) { throw new AssertionError(ex); }
}
}