|
|
@@ -11,6 +11,7 @@ import com.ruoyi.app.flashdelivery.dto.FlashDeliveryCreateRequest;
|
|
|
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryProofRequest;
|
|
|
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryPricingRequest;
|
|
|
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryQuoteRequest;
|
|
|
+import com.ruoyi.app.flashdelivery.dto.FlashDeliveryReasonRequest;
|
|
|
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryDeliverRequest;
|
|
|
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryRiderOrderPageView;
|
|
|
import com.ruoyi.app.flashdelivery.exception.FlashDeliveryQuoteChangedException;
|
|
|
@@ -51,6 +52,7 @@ import static com.ruoyi.system.domain.flash.FlashDeliveryStatus.ACCEPTED;
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
|
+import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
@@ -69,6 +71,7 @@ import static org.mockito.Mockito.when;
|
|
|
import static org.mockito.Mockito.doAnswer;
|
|
|
import static org.mockito.Mockito.inOrder;
|
|
|
import static org.mockito.Mockito.doThrow;
|
|
|
+import static org.mockito.Mockito.verifyNoInteractions;
|
|
|
|
|
|
class FlashDeliveryApplicationServiceTest {
|
|
|
private static ConfigurableListableBeanFactory originalBeanFactory;
|
|
|
@@ -384,7 +387,10 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
var parameters = query.getValue().getParamNameValuePairs().values();
|
|
|
assertTrue(sql.contains("status"));
|
|
|
assertTrue(sql.contains("rider_id is null"));
|
|
|
+ assertTrue(sql.contains("is_display"));
|
|
|
+ assertTrue(sql.contains("vehicle_type"));
|
|
|
assertTrue(sql.contains("scheduled_pickup_start_at"));
|
|
|
+ assertTrue(sql.contains("scheduled_pickup_end_at"));
|
|
|
assertTrue(sql.contains("st_distance_sphere"));
|
|
|
assertTrue(parameters.contains("WAITING_ACCEPTANCE"));
|
|
|
assertTrue(parameters.contains("NOW"));
|
|
|
@@ -397,6 +403,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
var summaryParameters = summaryQuery.getValue().getParamNameValuePairs().values();
|
|
|
assertTrue(summarySql.contains("rider_id is null"));
|
|
|
assertTrue(summarySql.contains("scheduled_pickup_start_at"));
|
|
|
+ assertTrue(summarySql.contains("scheduled_pickup_end_at"));
|
|
|
assertTrue(summarySql.contains("st_distance_sphere"));
|
|
|
assertTrue(summaryParameters.contains("WAITING_ACCEPTANCE"));
|
|
|
}
|
|
|
@@ -549,9 +556,11 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
assertNull(result.getDelivery().getLatitude());
|
|
|
assertNull(result.getDeliveryPinCode());
|
|
|
assertNull(result.getUserNote());
|
|
|
+ assertNull(result.getUser());
|
|
|
assertTrue(result.getSenderImageUrls().isEmpty());
|
|
|
assertTrue(result.getPickupImageUrls().isEmpty());
|
|
|
assertTrue(result.getDeliveryImageUrls().isEmpty());
|
|
|
+ verify(fixture.userMapper, never()).selectInfoUserByUserId(7L);
|
|
|
verify(fixture.imageMapper, never()).selectList(any());
|
|
|
verify(fixture.logMapper, never()).selectList(any());
|
|
|
}
|
|
|
@@ -579,6 +588,53 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
verify(fixture.logMapper, never()).selectList(any());
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ void userDetailReturnsRiderInfoAfterAcceptance() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, ACCEPTED);
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ InfoUser rider = new InfoUser();
|
|
|
+ rider.setUserId(8L);
|
|
|
+ rider.setUserType("2");
|
|
|
+ rider.setNickName("闪送骑士");
|
|
|
+ rider.setTelPhone("0933333333");
|
|
|
+ rider.setVehicleType("2");
|
|
|
+ rider.setLicensePlate("ABC-1234");
|
|
|
+ when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
|
|
|
+
|
|
|
+ var result = fixture.service.userDetail(7L, 10L);
|
|
|
+
|
|
|
+ assertEquals(8L, result.getRider().getUserId());
|
|
|
+ assertEquals("闪送骑士", result.getRider().getNickName());
|
|
|
+ assertEquals("0933333333", result.getRider().getTelPhone());
|
|
|
+ assertEquals("ABC-1234", result.getRider().getLicensePlate());
|
|
|
+ assertNull(result.getUser());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void assignedRiderDetailReturnsSenderInfo() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder order = order(10L, ACCEPTED);
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ InfoUser sender = new InfoUser();
|
|
|
+ sender.setUserId(7L);
|
|
|
+ sender.setNickName("寄件达人");
|
|
|
+ sender.setPhone("0911111111");
|
|
|
+ sender.setAvatar("https://example.com/avatar.png");
|
|
|
+ when(fixture.userMapper.selectInfoUserByUserId(7L)).thenReturn(sender);
|
|
|
+
|
|
|
+ var result = fixture.service.riderDetail(8L, 10L);
|
|
|
+
|
|
|
+ assertEquals(7L, result.getUser().getUserId());
|
|
|
+ assertEquals("寄件达人", result.getUser().getNickName());
|
|
|
+ assertEquals("0911111111", result.getUser().getPhone());
|
|
|
+ assertEquals("https://example.com/avatar.png", result.getUser().getAvatar());
|
|
|
+ assertNull(result.getRider());
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
void matchedReceiverCanViewAndConfirmButCannotCancelOrder() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
@@ -597,6 +653,188 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
eq("COMPLETED"), any(Date.class));
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ void senderRatesRiderOnCompletedOrder() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ when(fixture.orderMapper.rateRider(eq(10L), eq(7L), eq(4.5), any(Date.class))).thenReturn(1);
|
|
|
+
|
|
|
+ fixture.service.rateRider(7L, 10L, rating(4.5));
|
|
|
+
|
|
|
+ verify(fixture.orderMapper).rateRider(eq(10L), eq(7L), eq(4.5), any(Date.class));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void rateRiderRejectsUserOtherThanSender() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(88L, 10L, rating(4.5)));
|
|
|
+ verify(fixture.orderMapper, never()).rateRider(any(), any(), any(), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void rateRiderRejectsOrderNotCompletedOrWithoutRider() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder delivered = order(10L, "DELIVERED");
|
|
|
+ delivered.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(delivered);
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, rating(4.5)));
|
|
|
+
|
|
|
+ FlashDeliveryOrder completedWithoutRider = order(11L, "COMPLETED");
|
|
|
+ when(fixture.orderMapper.selectById(11L)).thenReturn(completedWithoutRider);
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 11L, rating(4.5)));
|
|
|
+
|
|
|
+ verify(fixture.orderMapper, never()).rateRider(any(), any(), any(), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void rateRiderRejectsAlreadyRatedOrder() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ order.setRiderStars(4.5);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, rating(3.0)));
|
|
|
+ verify(fixture.orderMapper, never()).rateRider(any(), any(), any(), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void rateRiderRejectsConcurrentDuplicateSubmission() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ when(fixture.orderMapper.rateRider(eq(10L), eq(7L), eq(4.5), any(Date.class))).thenReturn(0);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, rating(4.5)));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void rateRiderRejectsMissingOrOutOfRangeStars() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, null));
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, rating(null)));
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, rating(0.5)));
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.rateRider(7L, 10L, rating(5.5)));
|
|
|
+
|
|
|
+ verify(fixture.orderMapper, never()).selectById(any());
|
|
|
+ verify(fixture.orderMapper, never()).rateRider(any(), any(), any(), any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void userDetailReturnsRiderStars() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ order.setRiderStars(4.5);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
|
|
+
|
|
|
+ var view = fixture.service.userDetail(7L, 10L);
|
|
|
+
|
|
|
+ assertEquals(4.5, view.getRiderStars());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void userDetailRiderCarriesFlashAverageStars() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "ACCEPTED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ InfoUser rider = new InfoUser();
|
|
|
+ rider.setUserId(8L);
|
|
|
+ rider.setNickName("闪送骑手");
|
|
|
+ when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
|
|
|
+ when(fixture.orderMapper.selectRiderAverageStars(8L)).thenReturn(4.8);
|
|
|
+ when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
|
|
+
|
|
|
+ var view = fixture.service.userDetail(7L, 10L);
|
|
|
+
|
|
|
+ assertEquals(4.8, view.getRider().getStar());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void userDetailRiderFallsBackToDefaultStarsWithoutRatings() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "ACCEPTED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+ InfoUser rider = new InfoUser();
|
|
|
+ rider.setUserId(8L);
|
|
|
+ when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
|
|
|
+ when(fixture.orderMapper.selectRiderAverageStars(8L)).thenReturn(null);
|
|
|
+ when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
|
|
+
|
|
|
+ var view = fixture.service.userDetail(7L, 10L);
|
|
|
+
|
|
|
+ assertEquals(4.5, view.getRider().getStar());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void userDetailByOrderNoReturnsParticipantDetail() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ order.setRiderStars(4.5);
|
|
|
+ when(fixture.orderMapper.selectOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ InfoUser rider = new InfoUser();
|
|
|
+ rider.setUserId(8L);
|
|
|
+ when(fixture.userMapper.selectInfoUserByUserId(8L)).thenReturn(rider);
|
|
|
+ when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
|
|
+
|
|
|
+ var view = fixture.service.userDetailByOrderNo(7L, "FL-20260813-021");
|
|
|
+
|
|
|
+ assertEquals(10L, view.getId());
|
|
|
+ assertEquals(4.5, view.getRiderStars());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void userDetailByOrderNoRejectsNonParticipantAndUnknownOrderNo() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder order = order(10L, "COMPLETED");
|
|
|
+ order.setReceiverUserId(null);
|
|
|
+ when(fixture.orderMapper.selectOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ assertThrows(ServiceException.class,
|
|
|
+ () -> fixture.service.userDetailByOrderNo(88L, "FL-20260813-021"));
|
|
|
+
|
|
|
+ when(fixture.orderMapper.selectOne(any(Wrapper.class))).thenReturn(null);
|
|
|
+ assertThrows(ServiceException.class,
|
|
|
+ () -> fixture.service.userDetailByOrderNo(7L, "FL-UNKNOWN"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void riderDetailByOrderNoReturnsAssignedOrderDetail() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder order = order(10L, "ACCEPTED");
|
|
|
+ order.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectOne(any(Wrapper.class))).thenReturn(order);
|
|
|
+ when(fixture.userMapper.selectInfoUserByUserId(7L)).thenReturn(new InfoUser());
|
|
|
+ when(fixture.imageMapper.selectList(any(Wrapper.class))).thenReturn(List.of());
|
|
|
+
|
|
|
+ var view = fixture.service.riderDetailByOrderNo(8L, "FL-20260813-021");
|
|
|
+
|
|
|
+ assertEquals(10L, view.getId());
|
|
|
+ assertNotNull(view.getUser());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void riderDetailByOrderNoRejectsUnknownOrderNo() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ when(fixture.orderMapper.selectOne(any(Wrapper.class))).thenReturn(null);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class,
|
|
|
+ () -> fixture.service.riderDetailByOrderNo(8L, "FL-UNKNOWN"));
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
void orderWithoutCreationTimeReceiverBindingIsNotClaimedLater() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
@@ -637,6 +875,22 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
assertThrows(RuntimeException.class, () -> fixture.service.accept(8L, 10L));
|
|
|
}
|
|
|
|
|
|
+ // 预约单已过取件结束时间:即使骑手绕过列表直接持有订单 ID 请求接单,锁内校验也必须拒绝。
|
|
|
+ @Test
|
|
|
+ void expiredScheduledOrderCannotBeAcceptedEvenWhenItsIdIsKnown() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder order = order(10L, "WAITING_ACCEPTANCE");
|
|
|
+ Date now = new Date();
|
|
|
+ order.setDeliveryMode("SCHEDULED");
|
|
|
+ order.setScheduledPickupStartAt(new Date(now.getTime() - 31L * 60L * 1000L));
|
|
|
+ order.setScheduledPickupEndAt(new Date(now.getTime() - 60L * 1000L));
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.accept(8L, 10L));
|
|
|
+ verify(fixture.orderMapper, never()).accept(any(), any(), any());
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
void flashAcceptUsesRiderLockAndChecksAcceptancePolicyBeforeAtomicUpdate() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
@@ -668,6 +922,212 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
verify(fixture.orderMapper, never()).accept(any(), any(), any());
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ void acceptSuccessNotifiesSenderOfOrder() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder waiting = order(10L, "WAITING_ACCEPTANCE");
|
|
|
+ FlashDeliveryOrder accepted = order(10L, ACCEPTED);
|
|
|
+ accepted.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(waiting, accepted);
|
|
|
+ when(fixture.orderMapper.accept(eq(10L), eq(8L), any(Date.class))).thenReturn(1);
|
|
|
+
|
|
|
+ fixture.service.accept(8L, 10L);
|
|
|
+
|
|
|
+ verify(fixture.notificationService).notifyAccepted(waiting);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void acceptFailureDoesNotTriggerNotification() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order(10L, "WAITING_ACCEPTANCE"));
|
|
|
+ when(fixture.orderMapper.accept(eq(10L), eq(8L), any(Date.class))).thenReturn(0);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.accept(8L, 10L));
|
|
|
+
|
|
|
+ verify(fixture.notificationService, never()).notifyAccepted(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void pickupSuccessNotifiesSenderOfOrder() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder accepted = order(10L, ACCEPTED);
|
|
|
+ accepted.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(accepted);
|
|
|
+ // 逐站取件走 updateFulfilment 条件更新而非 transitionByRider。
|
|
|
+ when(fixture.orderMapper.updateFulfilment(any())).thenReturn(1);
|
|
|
+ FlashDeliveryProofRequest request = new FlashDeliveryProofRequest();
|
|
|
+ request.setImageUrls(List.of("https://example.com/pickup.jpg"));
|
|
|
+
|
|
|
+ fixture.service.pickup(8L, 10L, request);
|
|
|
+
|
|
|
+ verify(fixture.notificationService).notifyPickedUp(accepted);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void deliverSuccessNotifiesSenderOfOrder() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder pickedUp = order(10L, "PICKED_UP");
|
|
|
+ pickedUp.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(pickedUp);
|
|
|
+ when(fixture.orderMapper.transitionByRider(eq(10L), eq(8L), eq("PICKED_UP"),
|
|
|
+ eq("DELIVERED"), eq("delivered_at"), any())).thenReturn(1);
|
|
|
+ FlashDeliveryDeliverRequest request = new FlashDeliveryDeliverRequest();
|
|
|
+ request.setPinCode("4821");
|
|
|
+ request.setImageUrls(List.of("https://example.com/delivery.jpg"));
|
|
|
+
|
|
|
+ fixture.service.deliver(8L, 10L, request);
|
|
|
+
|
|
|
+ verify(fixture.notificationService).notifyDelivered(pickedUp);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void proofTransitionFailureDoesNotTriggerNotification() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ rider(fixture, 8L);
|
|
|
+ FlashDeliveryOrder accepted = order(10L, ACCEPTED);
|
|
|
+ accepted.setRiderId(8L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(accepted);
|
|
|
+ when(fixture.orderMapper.transitionByRider(eq(10L), eq(8L), eq("ACCEPTED"),
|
|
|
+ eq("PICKED_UP"), eq("picked_up_at"), any())).thenReturn(0);
|
|
|
+ FlashDeliveryProofRequest request = new FlashDeliveryProofRequest();
|
|
|
+ request.setImageUrls(List.of("https://example.com/pickup.jpg"));
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.pickup(8L, 10L, request));
|
|
|
+
|
|
|
+ verify(fixture.notificationService, never()).notifyPickedUp(any());
|
|
|
+ verify(fixture.notificationService, never()).notifyDelivered(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void userCancelSuccessNotifiesAssignedRider() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder accepted = order(10L, ACCEPTED);
|
|
|
+ accepted.setRiderId(9L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(accepted);
|
|
|
+ when(fixture.orderMapper.cancel(eq(10L), eq("ACCEPTED"), eq("USER"), eq(7L),
|
|
|
+ anyString(), any(Date.class))).thenReturn(1);
|
|
|
+
|
|
|
+ fixture.service.userCancel(7L, 10L, reason("改行程"));
|
|
|
+
|
|
|
+ verify(fixture.notificationService).notifyCancelled(accepted);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void adminCancelSuccessNotifiesAssignedRider() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder pickedUp = order(10L, "PICKED_UP");
|
|
|
+ pickedUp.setRiderId(9L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(pickedUp);
|
|
|
+ // 平台异常取消先写介入审计,再走条件取消。
|
|
|
+ when(fixture.orderMapper.update(any(), any())).thenReturn(1);
|
|
|
+ when(fixture.orderMapper.cancel(eq(10L), eq("PICKED_UP"), eq("ADMIN"), eq(1L),
|
|
|
+ anyString(), any(Date.class))).thenReturn(1);
|
|
|
+ FlashDeliveryReasonRequest request = reason("异常收口");
|
|
|
+ request.setProblemStopId(101L);
|
|
|
+ request.setHandlingResult("平台介入收口");
|
|
|
+ // 已取货订单必须记录物品去向和交接凭证。
|
|
|
+ request.setPickedGoodsDisposition("已取物品交回寄件人");
|
|
|
+ request.setHandoffImageUrls(List.of("https://example.com/handoff.jpg"));
|
|
|
+
|
|
|
+ fixture.service.adminCancel(1L, 10L, request);
|
|
|
+
|
|
|
+ verify(fixture.notificationService).notifyCancelled(pickedUp);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void cancelFailureDoesNotTriggerNotification() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder accepted = order(10L, ACCEPTED);
|
|
|
+ accepted.setRiderId(9L);
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(accepted);
|
|
|
+ when(fixture.orderMapper.cancel(eq(10L), eq("ACCEPTED"), eq("USER"), eq(7L),
|
|
|
+ anyString(), any(Date.class))).thenReturn(0);
|
|
|
+
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.userCancel(7L, 10L, reason("改行程")));
|
|
|
+
|
|
|
+ verify(fixture.notificationService, never()).notifyCancelled(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void excludedEventsNeverTriggerNotification() {
|
|
|
+ // 加小费、完成、过期预约自动取消三类事件不产生任何推送。
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ editableOrder(fixture);
|
|
|
+ when(fixture.orderMapper.updateWaitingTip(any(), eq(2))).thenReturn(1);
|
|
|
+ fixture.service.addTip(7L, 10L, tipRequest(20L, 2));
|
|
|
+
|
|
|
+ // 合并后的 complete 要求订单已送达且全部取货站完成。
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(order(10L, "DELIVERED"));
|
|
|
+ when(fixture.orderMapper.transitionByStatus(eq(10L), eq("DELIVERED"), eq("COMPLETED"),
|
|
|
+ eq("completed_at"), any(Date.class))).thenReturn(1);
|
|
|
+ assertTrue(fixture.service.complete(1L, "ADMIN", 10L));
|
|
|
+
|
|
|
+ when(fixture.orderMapper.cancelExpiredScheduled(eq(10L), any(Date.class))).thenReturn(1);
|
|
|
+ assertTrue(fixture.service.cancelExpiredScheduledOrder(10L, new Date()));
|
|
|
+
|
|
|
+ verifyNoInteractions(fixture.notificationService);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void immediateOrderIsVisibleAndNotifiesAvailableRiders() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ prepareCreate(fixture);
|
|
|
+ FlashDeliveryCreateRequest request = createRequest();
|
|
|
+ request.setDeliveryMode("NOW");
|
|
|
+ request.setScheduledPickupStartAt(null);
|
|
|
+ request.setScheduledPickupEndAt(null);
|
|
|
+
|
|
|
+ fixture.service.create(7L, request);
|
|
|
+
|
|
|
+ ArgumentCaptor<FlashDeliveryOrder> saved = ArgumentCaptor.forClass(FlashDeliveryOrder.class);
|
|
|
+ verify(fixture.orderMapper).insert(saved.capture());
|
|
|
+ assertTrue(saved.getValue().getIsDisplay());
|
|
|
+ verify(fixture.notificationService).notifyAvailable(saved.getValue());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void scheduledOrderStartsHiddenAndDoesNotNotifyBeforeWindow() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ prepareCreate(fixture);
|
|
|
+
|
|
|
+ fixture.service.create(7L, createRequest());
|
|
|
+
|
|
|
+ ArgumentCaptor<FlashDeliveryOrder> saved = ArgumentCaptor.forClass(FlashDeliveryOrder.class);
|
|
|
+ verify(fixture.orderMapper).insert(saved.capture());
|
|
|
+ assertFalse(saved.getValue().getIsDisplay());
|
|
|
+ verify(fixture.notificationService, never()).notifyAvailable(any());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void scheduledOrderBecomesVisibleAndNotifiesOnlyAfterAtomicOpenSucceeds() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryOrder scheduled = order(10L, "WAITING_ACCEPTANCE");
|
|
|
+ scheduled.setDeliveryMode("SCHEDULED");
|
|
|
+ scheduled.setIsDisplay(false);
|
|
|
+ Date now = new Date();
|
|
|
+ when(fixture.orderMapper.selectById(10L)).thenReturn(scheduled);
|
|
|
+ when(fixture.orderMapper.openScheduledForRiders(10L, now)).thenReturn(1);
|
|
|
+
|
|
|
+ assertTrue(fixture.service.openScheduledOrderForRiders(10L, now));
|
|
|
+
|
|
|
+ assertTrue(scheduled.getIsDisplay());
|
|
|
+ verify(fixture.notificationService).notifyAvailable(scheduled);
|
|
|
+
|
|
|
+ when(fixture.orderMapper.openScheduledForRiders(10L, now)).thenReturn(0);
|
|
|
+ assertFalse(fixture.service.openScheduledOrderForRiders(10L, now));
|
|
|
+ verify(fixture.notificationService).notifyAvailable(scheduled);
|
|
|
+ }
|
|
|
+
|
|
|
+ private FlashDeliveryReasonRequest reason(String text) {
|
|
|
+ FlashDeliveryReasonRequest request = new FlashDeliveryReasonRequest();
|
|
|
+ request.setReason(text);
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
@Test
|
|
|
void oversizedProofUrlIsRejectedBeforeStateTransition() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
@@ -1043,6 +1503,18 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
return request;
|
|
|
}
|
|
|
|
|
|
+ private void prepareCreate(Fixture fixture) {
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt()))
|
|
|
+ .thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone("0922222222")).thenReturn(List.of());
|
|
|
+ doAnswer(invocation -> {
|
|
|
+ FlashDeliveryOrder order = invocation.getArgument(0);
|
|
|
+ order.setId(99L);
|
|
|
+ return 1;
|
|
|
+ }).when(fixture.orderMapper).insert(any(FlashDeliveryOrder.class));
|
|
|
+ }
|
|
|
+
|
|
|
private FlashDeliveryAddressRequest address(String name, String phone, String lat, String lon) {
|
|
|
FlashDeliveryAddressRequest address = new FlashDeliveryAddressRequest();
|
|
|
address.setName(name);
|
|
|
@@ -1111,6 +1583,13 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
when(fixture.userMapper.selectInfoUserByUserId(riderId)).thenReturn(rider);
|
|
|
}
|
|
|
|
|
|
+ private com.ruoyi.app.flashdelivery.dto.FlashDeliveryRatingRequest rating(Double stars) {
|
|
|
+ com.ruoyi.app.flashdelivery.dto.FlashDeliveryRatingRequest request =
|
|
|
+ new com.ruoyi.app.flashdelivery.dto.FlashDeliveryRatingRequest();
|
|
|
+ request.setStars(stars);
|
|
|
+ return request;
|
|
|
+ }
|
|
|
+
|
|
|
private FlashDeliveryOrder order(Long id, String status) {
|
|
|
FlashDeliveryOrder order = new FlashDeliveryOrder();
|
|
|
order.setId(id);
|
|
|
@@ -1120,6 +1599,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
order.setServiceType("HELP_SEND");
|
|
|
order.setDeliveryType("NORMAL");
|
|
|
order.setStatus(status);
|
|
|
+ order.setIsDisplay(true);
|
|
|
order.setPackageType("DOCUMENT");
|
|
|
order.setQuantity(1);
|
|
|
order.setWeightRange("UP_TO_5_KG");
|
|
|
@@ -1478,10 +1958,11 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
final RiderDeliveryAcceptanceService acceptanceService = mock(RiderDeliveryAcceptanceService.class);
|
|
|
final com.ruoyi.system.service.PaymentMethodGateService paymentMethodGateService =
|
|
|
mock(com.ruoyi.system.service.PaymentMethodGateService.class);
|
|
|
+ final FlashDeliveryNotificationService notificationService = mock(FlashDeliveryNotificationService.class);
|
|
|
final FlashDeliveryApplicationService service = new FlashDeliveryApplicationService(
|
|
|
orderMapper, pricingMapper, imageMapper, logMapper, userMapper,
|
|
|
routeService, new FlashDeliveryPricingCalculator(), lockService, acceptanceService, stopMapper, adjustmentMapper,
|
|
|
- paymentMethodGateService);
|
|
|
+ paymentMethodGateService, notificationService);
|
|
|
|
|
|
Fixture() {
|
|
|
when(paymentMethodGateService.listAvailable(any(), any(), any())).thenReturn(List.of(
|