|
|
@@ -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;
|
|
|
@@ -50,11 +51,13 @@ 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;
|
|
|
import com.ruoyi.common.exception.ServiceException;
|
|
|
import static org.mockito.ArgumentMatchers.any;
|
|
|
+import static org.mockito.ArgumentMatchers.anyInt;
|
|
|
import static org.mockito.ArgumentMatchers.anyString;
|
|
|
import static org.mockito.ArgumentMatchers.eq;
|
|
|
import static org.mockito.ArgumentMatchers.isNull;
|
|
|
@@ -67,6 +70,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;
|
|
|
@@ -90,8 +94,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void quoteUsesServerRouteAndCurrentTimePricing() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3001, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3001, 600, "ROUTE"));
|
|
|
|
|
|
var quote = fixture.service.quote(request());
|
|
|
|
|
|
@@ -106,8 +110,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void quoteRejectsRoutesLongerThanFortyKilometres() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(40001, 1800, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(40001, 1800, "ROUTE"));
|
|
|
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.quote(request()));
|
|
|
}
|
|
|
@@ -169,8 +173,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
Fixture fixture = new Fixture();
|
|
|
when(fixture.userMapper.selectOrdinaryUserIdsByNormalizedPhone("0922222222"))
|
|
|
.thenReturn(List.of(88L));
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
doAnswer(invocation -> {
|
|
|
FlashDeliveryOrder order = invocation.getArgument(0);
|
|
|
order.setId(99L);
|
|
|
@@ -244,8 +248,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void createDoesNotBindReceiverWhenNormalizedPhoneMatchesMultipleUsers() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ 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("886912345678"))
|
|
|
.thenReturn(List.of(88L, 99L));
|
|
|
doAnswer(invocation -> {
|
|
|
@@ -266,8 +270,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void createLeavesReceiverUnboundWhenPhoneHasNoRegisteredUser() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ 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 -> {
|
|
|
@@ -336,9 +340,13 @@ 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(1));
|
|
|
assertTrue(parameters.contains("NOW"));
|
|
|
assertTrue(parameters.stream().anyMatch(Date.class::isInstance));
|
|
|
|
|
|
@@ -348,7 +356,9 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
String summarySql = summaryQuery.getValue().getSqlSegment().toLowerCase();
|
|
|
var summaryParameters = summaryQuery.getValue().getParamNameValuePairs().values();
|
|
|
assertTrue(summarySql.contains("rider_id is null"));
|
|
|
+ assertTrue(summarySql.contains("vehicle_type"));
|
|
|
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"));
|
|
|
}
|
|
|
@@ -501,9 +511,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());
|
|
|
}
|
|
|
@@ -531,6 +543,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();
|
|
|
@@ -549,6 +608,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();
|
|
|
@@ -589,6 +830,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 flashAcceptUsesRiderLockAndChecksExclusivityBeforeAtomicUpdate() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
@@ -620,6 +877,202 @@ 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);
|
|
|
+ when(fixture.orderMapper.transitionByRider(eq(10L), eq(8L), eq("ACCEPTED"),
|
|
|
+ eq("PICKED_UP"), eq("picked_up_at"), 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.cancel(eq(10L), eq("PICKED_UP"), eq("ADMIN"), eq(1L),
|
|
|
+ anyString(), any(Date.class))).thenReturn(1);
|
|
|
+
|
|
|
+ fixture.service.adminCancel(1L, 10L, reason("异常收口"));
|
|
|
+
|
|
|
+ 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));
|
|
|
+
|
|
|
+ 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();
|
|
|
@@ -655,7 +1108,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
void concurrentPricingUpdateIsRejected() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
when(fixture.pricingMapper.selectById(1L)).thenReturn(pricing());
|
|
|
- when(fixture.pricingMapper.countOverlapping("00:00", "12:00", 1L)).thenReturn(0);
|
|
|
+ when(fixture.pricingMapper.countOverlapping("00:00", "12:00", 1, 1L)).thenReturn(0);
|
|
|
when(fixture.pricingMapper.update(isNull(), any(Wrapper.class))).thenReturn(0);
|
|
|
FlashDeliveryPricingRequest request = pricingRequest();
|
|
|
request.setEndTime("12:00");
|
|
|
@@ -670,7 +1123,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
void overlappingPricingPeriodIsRejectedBeforeInsert() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
FlashDeliveryPricingRequest request = pricingRequest();
|
|
|
- when(fixture.pricingMapper.countOverlapping("00:00", "24:00", null)).thenReturn(1);
|
|
|
+ when(fixture.pricingMapper.countOverlapping("00:00", "24:00", 1, null)).thenReturn(1);
|
|
|
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.createPricing(1L, request));
|
|
|
|
|
|
@@ -708,7 +1161,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
void createPricingPersistsImmediatelyActiveTimePeriod() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
FlashDeliveryPricingRequest request = pricingRequest();
|
|
|
- when(fixture.pricingMapper.countOverlapping("00:00", "24:00", null)).thenReturn(0);
|
|
|
+ when(fixture.pricingMapper.countOverlapping("00:00", "24:00", 1, null)).thenReturn(0);
|
|
|
|
|
|
fixture.service.createPricing(7L, request);
|
|
|
|
|
|
@@ -720,7 +1173,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
|
|
|
InOrder writeOrder = inOrder(fixture.pricingMapper);
|
|
|
writeOrder.verify(fixture.pricingMapper)
|
|
|
- .countOverlapping("00:00", "24:00", null);
|
|
|
+ .countOverlapping("00:00", "24:00", 1, null);
|
|
|
writeOrder.verify(fixture.pricingMapper).insert(any(FlashDeliveryPricing.class));
|
|
|
}
|
|
|
|
|
|
@@ -737,18 +1190,18 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.quote(request()));
|
|
|
|
|
|
- verify(fixture.routeService, never()).calculate(any(), any());
|
|
|
+ verify(fixture.routeService, never()).calculate(any(), any(), anyInt());
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
void multipleCurrentPricingPeriodsFailExplicitlyBeforeRouteLookup() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString()))
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt()))
|
|
|
.thenReturn(List.of(pricing(), pricing()));
|
|
|
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.quote(request()));
|
|
|
|
|
|
- verify(fixture.routeService, never()).calculate(any(), any());
|
|
|
+ verify(fixture.routeService, never()).calculate(any(), any(), anyInt());
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -759,12 +1212,14 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
when(fixture.pricingMapper.update(isNull(), any(Wrapper.class))).thenReturn(1);
|
|
|
FlashDeliveryPricingRequest request = pricingRequest();
|
|
|
request.setConfigVersion(1);
|
|
|
+ request.setVehicleType(2);
|
|
|
|
|
|
- fixture.service.updatePricing(9L, 1L, request);
|
|
|
+ FlashDeliveryPricing updated = fixture.service.updatePricing(9L, 1L, request);
|
|
|
+ assertEquals(2, updated.getVehicleType());
|
|
|
|
|
|
InOrder writeOrder = inOrder(fixture.pricingMapper);
|
|
|
writeOrder.verify(fixture.pricingMapper)
|
|
|
- .countOverlapping("00:00", "24:00", 1L);
|
|
|
+ .countOverlapping("00:00", "24:00", 2, 1L);
|
|
|
writeOrder.verify(fixture.pricingMapper).update(isNull(), any(Wrapper.class));
|
|
|
}
|
|
|
|
|
|
@@ -782,8 +1237,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void urgentQuoteReturnsSeparatedFeesAndUsesScheduledPickupTime() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime("16:30")).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(6000, 900, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime("16:30", 1)).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(6000, 900, "ROUTE"));
|
|
|
FlashDeliveryQuoteRequest request = request();
|
|
|
request.setDeliveryType("URGENT");
|
|
|
request.setTipAmount(5L);
|
|
|
@@ -800,18 +1255,18 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
var quote = fixture.service.quote(request);
|
|
|
|
|
|
assertEquals(45L, quote.getDistanceFee());
|
|
|
- assertEquals(135L, quote.getBaseDeliveryFee());
|
|
|
+ assertEquals(90L, quote.getBaseDeliveryFee());
|
|
|
assertEquals(27L, quote.getUrgentFee());
|
|
|
assertEquals(5L, quote.getTipAmount());
|
|
|
assertEquals(167L, quote.getAmount());
|
|
|
- verify(fixture.pricingMapper).selectAtTime("16:30");
|
|
|
+ verify(fixture.pricingMapper).selectAtTime("16:30", 1);
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
void createRejectsChangedQuoteAndReturnsLatestQuoteWithoutInsert() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
FlashDeliveryCreateRequest request = createRequest();
|
|
|
request.setQuotedAmount(91L);
|
|
|
|
|
|
@@ -826,8 +1281,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void createDefaultsPayTypeToCashAndStoresTransferSelection() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
doAnswer(invocation -> {
|
|
|
FlashDeliveryOrder order = invocation.getArgument(0);
|
|
|
order.setId(99L);
|
|
|
@@ -853,8 +1308,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
void createPassesThroughPayTypeAsProvided() {
|
|
|
// 05cc7e4 放宽 normalizePayType:不再限定 4/6,任意 payType 透传存储(缺省仍为现金)。
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
FlashDeliveryCreateRequest request = createRequest();
|
|
|
request.setPayType("2");
|
|
|
|
|
|
@@ -887,8 +1342,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
@Test
|
|
|
void quoteAcceptsAllFourWeightRanges() {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
|
|
|
for (String weightRange : List.of("UP_TO_5_KG", "OVER_5_TO_10_KG",
|
|
|
"OVER_10_TO_15_KG", "OVER_15_TO_20_KG")) {
|
|
|
@@ -904,8 +1359,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
for (String weightRange : List.of("UP_TO_5_KG", "OVER_5_TO_10_KG",
|
|
|
"OVER_10_TO_15_KG", "OVER_15_TO_20_KG")) {
|
|
|
Fixture fixture = new Fixture();
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
doAnswer(invocation -> {
|
|
|
FlashDeliveryOrder order = invocation.getArgument(0);
|
|
|
order.setId(99L);
|
|
|
@@ -935,12 +1390,45 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ @Test
|
|
|
+ void quoteUsesSelectedVehiclePricingAndRejectsUnsupportedVehicle() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ FlashDeliveryPricing carPricing = pricing();
|
|
|
+ carPricing.setId(2L);
|
|
|
+ carPricing.setVehicleType(2);
|
|
|
+ carPricing.setStartingFare(180L);
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), eq(2))).thenReturn(List.of(carPricing));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+
|
|
|
+ FlashDeliveryQuoteRequest carRequest = request();
|
|
|
+ carRequest.setVehicleType(2);
|
|
|
+ assertEquals(180L, fixture.service.quote(carRequest).getAmount());
|
|
|
+ verify(fixture.routeService).calculate(any(), any(), eq(2));
|
|
|
+
|
|
|
+ FlashDeliveryQuoteRequest missingVehicle = request();
|
|
|
+ missingVehicle.setVehicleType(null);
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.quote(missingVehicle));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void quoteAllowsMissingWeightRangeAndSpecification() {
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), eq(1))).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(3200, 600, "ROUTE"));
|
|
|
+ FlashDeliveryQuoteRequest request = request();
|
|
|
+ request.setWeightRange(null);
|
|
|
+ request.setSpecification(null);
|
|
|
+
|
|
|
+ assertDoesNotThrow(() -> fixture.service.quote(request));
|
|
|
+ }
|
|
|
+
|
|
|
private FlashDeliveryQuoteRequest request() {
|
|
|
FlashDeliveryQuoteRequest request = new FlashDeliveryQuoteRequest();
|
|
|
request.setServiceType("HELP_SEND");
|
|
|
request.setDeliveryType("NORMAL");
|
|
|
request.setPackageType("DOCUMENT");
|
|
|
request.setQuantity(1);
|
|
|
+ request.setVehicleType(1);
|
|
|
request.setWeightRange("UP_TO_5_KG");
|
|
|
request.setSpecification("30 x 20 x 10 cm");
|
|
|
request.setTipAmount(0L);
|
|
|
@@ -956,6 +1444,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
FlashDeliveryQuoteRequest quote = request();
|
|
|
request.setServiceType(quote.getServiceType());
|
|
|
request.setDeliveryType(quote.getDeliveryType());
|
|
|
+ request.setVehicleType(quote.getVehicleType());
|
|
|
request.setPackageType(quote.getPackageType());
|
|
|
request.setQuantity(quote.getQuantity());
|
|
|
request.setWeightRange(quote.getWeightRange());
|
|
|
@@ -979,6 +1468,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);
|
|
|
@@ -993,6 +1494,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
private FlashDeliveryPricing pricing() {
|
|
|
FlashDeliveryPricing pricing = new FlashDeliveryPricing();
|
|
|
pricing.setId(1L);
|
|
|
+ pricing.setVehicleType(1);
|
|
|
pricing.setStartTime("00:00");
|
|
|
pricing.setEndTime("24:00");
|
|
|
pricing.setStartingDistance(new BigDecimal("3.00"));
|
|
|
@@ -1007,6 +1509,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
|
|
|
private FlashDeliveryPricingRequest pricingRequest() {
|
|
|
FlashDeliveryPricingRequest request = new FlashDeliveryPricingRequest();
|
|
|
+ request.setVehicleType(1);
|
|
|
request.setStartTime("00:00");
|
|
|
request.setEndTime("24:00");
|
|
|
request.setStartingDistance(new BigDecimal("3.00"));
|
|
|
@@ -1043,9 +1546,17 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
rider.setUserId(riderId);
|
|
|
rider.setUserType("2");
|
|
|
rider.setDeliveryType("FLASH");
|
|
|
+ rider.setVehicleType("1");
|
|
|
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);
|
|
|
@@ -1054,6 +1565,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");
|
|
|
@@ -1125,14 +1637,14 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
assertEquals(4500, quote.getDistanceMeters());
|
|
|
assertEquals(900, quote.getEstimatedDurationSeconds());
|
|
|
assertEquals(23L, quote.getDistanceFee());
|
|
|
- assertEquals(113L, quote.getBaseDeliveryFee());
|
|
|
+ assertEquals(90L, quote.getBaseDeliveryFee());
|
|
|
assertEquals(10L, quote.getTipAmount());
|
|
|
assertEquals(123L, quote.getAmount());
|
|
|
assertEquals(2, quote.getOrderVersion());
|
|
|
assertEquals(1, quote.getPricingVersion());
|
|
|
assertEquals("pickup road", order.getPickupAddress());
|
|
|
// 改址报价必须查询运价表(按定价时点取最新),不再沿用订单快照
|
|
|
- verify(fixture.pricingMapper).selectAtTime(anyString());
|
|
|
+ verify(fixture.pricingMapper).selectAtTime(anyString(), anyInt());
|
|
|
verify(fixture.orderMapper, never()).updateWaitingAddress(any(), any());
|
|
|
verify(fixture.logMapper, never()).insert(any(com.ruoyi.system.domain.flash.FlashDeliveryOrderLog.class));
|
|
|
}
|
|
|
@@ -1214,7 +1726,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.updateAddress(user, 10L, new FlashDeliveryAddressConfirmRequest()));
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.addTip(user, 10L, tipRequest(5L, 2)));
|
|
|
}
|
|
|
- verify(fixture.routeService, never()).calculate(any(), any());
|
|
|
+ verify(fixture.routeService, never()).calculate(any(), any(), anyInt());
|
|
|
verify(fixture.orderMapper, never()).updateWaitingTip(any(), any());
|
|
|
}
|
|
|
|
|
|
@@ -1258,7 +1770,7 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
input.setDelivery(request().getDelivery());
|
|
|
input.getPickup().setPhone("");
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.quoteAddress(7L, 10L, input));
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(40001, 1000, "GOOGLE"));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(40001, 1000, "GOOGLE"));
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.quoteAddress(7L, 10L, addressChange()));
|
|
|
verify(fixture.orderMapper, never()).updateWaitingAddress(any(), any());
|
|
|
}
|
|
|
@@ -1289,8 +1801,8 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
assertThrows(ServiceException.class, () -> fixture.service.addTip(7L, 10L, tipRequest(20L, 2)));
|
|
|
assertEquals(30L, order.getTipAmount());
|
|
|
verify(fixture.orderMapper).updateWaitingTip(any(), eq(2));
|
|
|
- verify(fixture.routeService, never()).calculate(any(), any());
|
|
|
- verify(fixture.pricingMapper, never()).selectAtTime(anyString());
|
|
|
+ verify(fixture.routeService, never()).calculate(any(), any(), anyInt());
|
|
|
+ verify(fixture.pricingMapper, never()).selectAtTime(anyString(), anyInt());
|
|
|
}
|
|
|
|
|
|
@Test
|
|
|
@@ -1362,9 +1874,9 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
order.setDistance(new BigDecimal("1.00"));
|
|
|
order.setFreight(15L);
|
|
|
when(fixture.orderMapper.selectById(10L)).thenReturn(order);
|
|
|
- when(fixture.routeService.calculate(any(), any())).thenReturn(new RouteDistance(4500, 900, "GOOGLE"));
|
|
|
+ when(fixture.routeService.calculate(any(), any(), anyInt())).thenReturn(new RouteDistance(4500, 900, "GOOGLE"));
|
|
|
// 031 后改址按订单定价时点读最新运价:stub 与订单快照同值,金额断言维持不变
|
|
|
- when(fixture.pricingMapper.selectAtTime(anyString())).thenReturn(List.of(pricing()));
|
|
|
+ when(fixture.pricingMapper.selectAtTime(anyString(), anyInt())).thenReturn(List.of(pricing()));
|
|
|
return order;
|
|
|
}
|
|
|
|
|
|
@@ -1407,9 +1919,11 @@ class FlashDeliveryApplicationServiceTest {
|
|
|
final FlashDeliveryRouteService routeService = mock(FlashDeliveryRouteService.class);
|
|
|
final RiderDeliveryLockService lockService = mock(RiderDeliveryLockService.class);
|
|
|
final RiderDeliveryExclusivityService exclusivityService = mock(RiderDeliveryExclusivityService.class);
|
|
|
+ final FlashDeliveryNotificationService notificationService = mock(FlashDeliveryNotificationService.class);
|
|
|
final FlashDeliveryApplicationService service = new FlashDeliveryApplicationService(
|
|
|
orderMapper, pricingMapper, imageMapper, logMapper, userMapper,
|
|
|
- routeService, new FlashDeliveryPricingCalculator(), lockService, exclusivityService);
|
|
|
+ routeService, new FlashDeliveryPricingCalculator(), lockService, exclusivityService,
|
|
|
+ notificationService);
|
|
|
|
|
|
Fixture() {
|
|
|
when(lockService.withLock(any(), any())).thenAnswer(invocation -> {
|