|
@@ -0,0 +1,226 @@
|
|
|
|
|
+package com.ruoyi.app.order;
|
|
|
|
|
+
|
|
|
|
|
+import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
|
|
|
|
+import com.ruoyi.common.core.domain.entity.SysDictData;
|
|
|
|
|
+import com.ruoyi.common.exception.ServiceException;
|
|
|
|
|
+import com.ruoyi.common.utils.DictUtils;
|
|
|
|
|
+import com.ruoyi.common.utils.spring.SpringUtils;
|
|
|
|
|
+import com.ruoyi.system.domain.InfoUser;
|
|
|
|
|
+import com.ruoyi.system.mapper.flash.FlashDeliveryOrderMapper;
|
|
|
|
|
+import com.ruoyi.system.service.IInfoUserService;
|
|
|
|
|
+import com.ruoyi.system.service.IPosOrderService;
|
|
|
|
|
+import org.junit.jupiter.api.AfterAll;
|
|
|
|
|
+import org.junit.jupiter.api.BeforeAll;
|
|
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
|
|
+import org.mockito.MockedStatic;
|
|
|
|
|
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
|
|
|
|
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
|
|
|
|
+import org.springframework.context.support.StaticMessageSource;
|
|
|
|
|
+import org.springframework.test.util.ReflectionTestUtils;
|
|
|
|
|
+
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
|
|
|
+import static org.junit.jupiter.api.Assertions.assertThrows;
|
|
|
|
|
+import static org.mockito.ArgumentMatchers.any;
|
|
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
|
|
+import static org.mockito.Mockito.mockStatic;
|
|
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
|
|
+
|
|
|
|
|
+class RiderDeliveryAcceptanceServiceTest {
|
|
|
|
|
+ private static ConfigurableListableBeanFactory originalBeanFactory;
|
|
|
|
|
+
|
|
|
|
|
+ @BeforeAll
|
|
|
|
|
+ static void initializeMessages() {
|
|
|
|
|
+ originalBeanFactory = (ConfigurableListableBeanFactory)
|
|
|
|
|
+ ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
|
|
|
|
|
+ DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
|
|
|
|
|
+ StaticMessageSource messageSource = new StaticMessageSource();
|
|
|
|
|
+ messageSource.setUseCodeAsDefaultMessage(true);
|
|
|
|
|
+ beanFactory.registerSingleton("messageSource", messageSource);
|
|
|
|
|
+ new SpringUtils().postProcessBeanFactory(beanFactory);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @AfterAll
|
|
|
|
|
+ static void restoreMessages() {
|
|
|
|
|
+ new SpringUtils().postProcessBeanFactory(originalBeanFactory);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void missingFoodLimitFallsBackToOneAndRejectsSecondFoodOrder() {
|
|
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
|
|
+ fixture.activeFood(1L);
|
|
|
|
|
+ fixture.activeFlash(0L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ dictionaries.when(() -> DictUtils.getDictCache("sys_rider_flash_active_order_limit"))
|
|
|
|
|
+ .thenReturn(List.of(dictionary("10")));
|
|
|
|
|
+ dictionaries.when(() -> DictUtils.getDictCache("sys_rider_total_active_order_limit"))
|
|
|
|
|
+ .thenReturn(List.of(dictionary("10")));
|
|
|
|
|
+
|
|
|
|
|
+ ServiceException error = assertThrows(ServiceException.class,
|
|
|
|
|
+ () -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+
|
|
|
|
|
+ assertEquals("rider.accept.food.limit.reached", error.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void malformedFoodLimitsFallBackToOneButValidTwoAllowsOneActiveOrder() {
|
|
|
|
|
+ for (String invalid : new String[] {null, "", "0", "-1", "1.5", "abc", "21", "999999999999"}) {
|
|
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
|
|
+ fixture.activeFood(1L);
|
|
|
|
|
+ fixture.activeFlash(0L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ stubLimits(dictionaries, invalid, "10", "10");
|
|
|
|
|
+ assertThrows(ServiceException.class, () -> fixture.service.assertCanAcceptFood(123L), invalid);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
|
|
+ fixture.activeFood(1L);
|
|
|
|
|
+ fixture.activeFlash(0L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ stubLimits(dictionaries, "2", "10", "2");
|
|
|
|
|
+ assertDoesNotThrow(() -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void normalFlashChecksItsOwnLimitAndTheCrossTypeTotalLimit() {
|
|
|
|
|
+ Fixture flashFull = new Fixture();
|
|
|
|
|
+ flashFull.activeFood(0L);
|
|
|
|
|
+ flashFull.activeFlash(1L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ stubLimits(dictionaries, "10", "1", "10");
|
|
|
|
|
+ ServiceException error = assertThrows(ServiceException.class,
|
|
|
|
|
+ () -> flashFull.service.assertCanAcceptFlash(123L, "NORMAL"));
|
|
|
|
|
+ assertEquals("rider.accept.flash.limit.reached", error.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Fixture totalFull = new Fixture();
|
|
|
|
|
+ totalFull.activeFood(1L);
|
|
|
|
|
+ totalFull.activeFlash(0L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ stubLimits(dictionaries, "10", "10", "1");
|
|
|
|
|
+ ServiceException error = assertThrows(ServiceException.class,
|
|
|
|
|
+ () -> totalFull.service.assertCanAcceptFlash(123L, "NORMAL"));
|
|
|
|
|
+ assertEquals("rider.accept.total.limit.reached", error.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void missingOrInvalidFlashAndTotalLimitsAlsoFallBackToOne() {
|
|
|
|
|
+ Fixture flashFull = new Fixture();
|
|
|
|
|
+ flashFull.activeFood(0L);
|
|
|
|
|
+ flashFull.activeFlash(1L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ stubLimits(dictionaries, "10", "21", "10");
|
|
|
|
|
+ assertMessage("rider.accept.flash.limit.reached",
|
|
|
|
|
+ () -> flashFull.service.assertCanAcceptFlash(123L, "NORMAL"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Fixture totalFull = new Fixture();
|
|
|
|
|
+ totalFull.activeFood(1L);
|
|
|
|
|
+ totalFull.activeFlash(0L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ dictionaries.when(() -> DictUtils.getDictCache("sys_rider_food_active_order_limit"))
|
|
|
|
|
+ .thenReturn(List.of(dictionary("10")));
|
|
|
|
|
+ assertMessage("rider.accept.total.limit.reached",
|
|
|
|
|
+ () -> totalFull.service.assertCanAcceptFood(123L));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void urgentDeliveryRemainsExclusiveAcrossFoodAndFlashOrders() {
|
|
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
|
|
+ fixture.activeFood(1L);
|
|
|
|
|
+ fixture.activeFlash(0L, 0L);
|
|
|
|
|
+ try (MockedStatic<DictUtils> dictionaries = mockStatic(DictUtils.class)) {
|
|
|
|
|
+ stubLimits(dictionaries, "10", "10", "10");
|
|
|
|
|
+ ServiceException error = assertThrows(ServiceException.class,
|
|
|
|
|
+ () -> fixture.service.assertCanAcceptFlash(123L, "URGENT"));
|
|
|
|
|
+ assertEquals("flash.delivery.rider.exclusive.conflict", error.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Test
|
|
|
|
|
+ void rejectsRidersThatAreNotApprovedEnabledOnlineAndTypeCompatible() {
|
|
|
|
|
+ Fixture fixture = new Fixture();
|
|
|
|
|
+
|
|
|
|
|
+ fixture.rider.setAuditStatus("0");
|
|
|
|
|
+ assertMessage("no.user.state.no.audit", () -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+
|
|
|
|
|
+ fixture.rider.setAuditStatus("1");
|
|
|
|
|
+ fixture.rider.setStatus("1");
|
|
|
|
|
+ assertMessage("no.user.stop", () -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+
|
|
|
|
|
+ fixture.rider.setStatus("0");
|
|
|
|
|
+ fixture.rider.setOffline("1");
|
|
|
|
|
+ assertMessage("rider.accept.offline", () -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+
|
|
|
|
|
+ fixture.rider.setOffline("0");
|
|
|
|
|
+ fixture.rider.setUserType("0");
|
|
|
|
|
+ assertMessage("rider.operation.role.required", () -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+
|
|
|
|
|
+ fixture.rider.setUserType("2");
|
|
|
|
|
+ fixture.rider.setDeliveryType("FLASH");
|
|
|
|
|
+ assertMessage("no.user.delivery.type.not.enabled", () -> fixture.service.assertCanAcceptFood(123L));
|
|
|
|
|
+
|
|
|
|
|
+ fixture.rider.setDeliveryType("FOOD");
|
|
|
|
|
+ assertMessage("flash.delivery.rider.type.not.enabled",
|
|
|
|
|
+ () -> fixture.service.assertCanAcceptFlash(123L, "NORMAL"));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void assertMessage(String expected, Runnable call) {
|
|
|
|
|
+ assertEquals(expected, assertThrows(ServiceException.class, call::run).getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static void stubLimits(MockedStatic<DictUtils> dictionaries,
|
|
|
|
|
+ String food, String flash, String total) {
|
|
|
|
|
+ dictionaries.when(() -> DictUtils.getDictCache("sys_rider_food_active_order_limit"))
|
|
|
|
|
+ .thenReturn(List.of(dictionary(food)));
|
|
|
|
|
+ dictionaries.when(() -> DictUtils.getDictCache("sys_rider_flash_active_order_limit"))
|
|
|
|
|
+ .thenReturn(List.of(dictionary(flash)));
|
|
|
|
|
+ dictionaries.when(() -> DictUtils.getDictCache("sys_rider_total_active_order_limit"))
|
|
|
|
|
+ .thenReturn(List.of(dictionary(total)));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static SysDictData dictionary(String value) {
|
|
|
|
|
+ SysDictData data = new SysDictData();
|
|
|
|
|
+ data.setDictValue(value);
|
|
|
|
|
+ return data;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static class Fixture {
|
|
|
|
|
+ final IInfoUserService riders = mock(IInfoUserService.class);
|
|
|
|
|
+ final IPosOrderService foodOrders = mock(IPosOrderService.class);
|
|
|
|
|
+ final FlashDeliveryOrderMapper flashOrders = mock(FlashDeliveryOrderMapper.class);
|
|
|
|
|
+ final InfoUser rider = eligibleRider();
|
|
|
|
|
+ final RiderDeliveryAcceptanceService service =
|
|
|
|
|
+ new RiderDeliveryAcceptanceService(riders, foodOrders, flashOrders);
|
|
|
|
|
+
|
|
|
|
|
+ Fixture() {
|
|
|
|
|
+ when(riders.getById(123L)).thenReturn(rider);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void activeFood(long count) {
|
|
|
|
|
+ when(foodOrders.count(any(Wrapper.class))).thenReturn(count);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ void activeFlash(long all, long urgent) {
|
|
|
|
|
+ when(flashOrders.selectCount(any(Wrapper.class))).thenReturn(all, urgent);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static InfoUser eligibleRider() {
|
|
|
|
|
+ InfoUser rider = new InfoUser();
|
|
|
|
|
+ rider.setUserId(123L);
|
|
|
|
|
+ rider.setUserType("2");
|
|
|
|
|
+ rider.setAuditStatus("1");
|
|
|
|
|
+ rider.setStatus("0");
|
|
|
|
|
+ rider.setOffline("0");
|
|
|
|
|
+ rider.setDeliveryType("FOOD,FLASH");
|
|
|
|
|
+ return rider;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|