| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553 |
- package com.ruoyi.app.flashdelivery.service;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONObject;
- import com.ruoyi.app.push.ApnsPushSender;
- import com.ruoyi.app.push.BusinessPushService;
- import com.ruoyi.app.push.FcmPushSender;
- import com.ruoyi.app.utils.PayPush;
- import com.ruoyi.app.utils.event.PushEvent;
- import com.ruoyi.app.utils.event.PushEventListener;
- import com.ruoyi.common.utils.LocaleUtils;
- import com.ruoyi.common.utils.spring.SpringUtils;
- import com.ruoyi.framework.manager.AsyncManager;
- import com.ruoyi.system.domain.PushMessage;
- import com.ruoyi.system.domain.flash.FlashDeliveryOrderStop;
- import com.ruoyi.system.service.IInfoUserService;
- import com.ruoyi.system.service.IPushMessageService;
- import com.ruoyi.app.utils.event.PushEventService;
- import com.ruoyi.app.order.dto.OrderPushBodyDto;
- import com.ruoyi.app.push.BusinessPushProperties;
- import com.ruoyi.app.push.NativeRiderBatchPushService;
- import com.ruoyi.app.push.UserNotificationScene;
- import com.ruoyi.system.domain.InfoUser;
- import com.ruoyi.system.domain.RiderPosition;
- import com.ruoyi.common.core.domain.entity.SysDictData;
- import com.ruoyi.system.domain.flash.FlashDeliveryOrder;
- import com.ruoyi.system.mapper.InfoUserMapper;
- import com.ruoyi.system.mapper.RiderPositionMapper;
- import com.ruoyi.common.utils.DictUtils;
- import org.junit.jupiter.api.Test;
- import org.mockito.MockedStatic;
- import org.springframework.context.ApplicationEventPublisher;
- import org.springframework.context.MessageSource;
- import org.springframework.context.support.ResourceBundleMessageSource;
- import org.springframework.test.util.ReflectionTestUtils;
- import org.springframework.transaction.support.TransactionSynchronization;
- import org.springframework.transaction.support.TransactionSynchronizationManager;
- import java.math.BigDecimal;
- import java.util.List;
- import java.util.ArrayList;
- import java.util.Locale;
- import java.util.Set;
- import java.util.TimerTask;
- import java.util.concurrent.ScheduledExecutorService;
- import static org.mockito.ArgumentMatchers.any;
- import static org.mockito.ArgumentMatchers.anyString;
- import static org.mockito.ArgumentMatchers.eq;
- import static org.mockito.ArgumentMatchers.same;
- import static org.mockito.Mockito.doNothing;
- import static org.mockito.Mockito.mock;
- import static org.mockito.Mockito.never;
- import static org.mockito.Mockito.spy;
- import static org.mockito.Mockito.timeout;
- import static org.mockito.Mockito.verify;
- import static org.mockito.Mockito.when;
- import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
- import static org.junit.jupiter.api.Assertions.assertEquals;
- import static org.junit.jupiter.api.Assertions.assertFalse;
- import static org.junit.jupiter.api.Assertions.assertTrue;
- import static org.mockito.Mockito.doAnswer;
- import static org.mockito.Mockito.doThrow;
- import static org.mockito.Mockito.mockStatic;
- class FlashDeliveryNotificationServiceTest {
- @Test
- void successfulUserNotificationsCarryScenesWithoutDeviceAddressAfterCommit() {
- InfoUserMapper users = mock(InfoUserMapper.class);
- InfoUser sender = new InfoUser(); sender.setUserId(7L);
- when(users.selectById(7L)).thenReturn(sender);
- FlashDeliveryNotificationService service = new FlashDeliveryNotificationService(users, mock(PushEventService.class));
- java.util.List<com.alibaba.fastjson.JSONObject> bodies = new java.util.ArrayList<>();
- try (var spring = org.mockito.Mockito.mockStatic(com.ruoyi.common.utils.spring.SpringUtils.class)) {
- spring.when(() -> com.ruoyi.common.utils.spring.SpringUtils.getBean("scheduledExecutorService"))
- .thenReturn(mock(java.util.concurrent.ScheduledExecutorService.class));
- com.ruoyi.framework.manager.AsyncManager manager = mock(com.ruoyi.framework.manager.AsyncManager.class);
- org.mockito.Mockito.doAnswer(call -> { ((java.util.TimerTask) call.getArgument(0)).run(); return null; })
- .when(manager).execute(any(java.util.TimerTask.class));
- try (var async = org.mockito.Mockito.mockStatic(com.ruoyi.framework.manager.AsyncManager.class);
- var push = org.mockito.Mockito.mockStatic(com.ruoyi.app.utils.PayPush.class)) {
- async.when(com.ruoyi.framework.manager.AsyncManager::me).thenReturn(manager);
- push.when(() -> com.ruoyi.app.utils.PayPush.userPushHandleLocal(any(), any(), eq(7L),
- org.mockito.ArgumentMatchers.isNull(), anyString(), anyString(), anyString(), anyString(), anyString()))
- .thenAnswer(call -> { bodies.add(com.alibaba.fastjson.JSON.parseObject(call.getArgument(6, String.class))); return null; });
- org.springframework.transaction.support.TransactionSynchronizationManager.initSynchronization();
- try {
- service.notifyAccepted(order()); service.notifyPickedUp(order()); service.notifyDelivered(order());
- org.junit.jupiter.api.Assertions.assertTrue(bodies.isEmpty());
- org.springframework.transaction.support.TransactionSynchronizationManager.getSynchronizations()
- .forEach(sync -> sync.afterCommit());
- org.junit.jupiter.api.Assertions.assertEquals(List.of("RIDER_ACCEPTED", "ORDER_DELIVERING", "ORDER_DELIVERED"),
- bodies.stream().map(body -> body.getString("eventScene")).toList());
- bodies.forEach(body -> org.junit.jupiter.api.Assertions.assertEquals(3, body.getIntValue("pushType")));
- } finally {
- org.springframework.transaction.support.TransactionSynchronizationManager.clearSynchronization();
- }
- }
- }
- }
- @Test
- void rollbackSkipsLookupAndPreparationFailureDoesNotEscapeBusinessOperation() {
- InfoUserMapper users = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = new FlashDeliveryNotificationService(users, mock(PushEventService.class));
- org.springframework.transaction.support.TransactionSynchronizationManager.initSynchronization();
- try {
- service.notifyAccepted(order());
- org.springframework.transaction.support.TransactionSynchronizationManager.getSynchronizations()
- .forEach(sync -> sync.afterCompletion(1));
- verify(users, never()).selectById(any());
- } finally {
- org.springframework.transaction.support.TransactionSynchronizationManager.clearSynchronization();
- }
- when(users.selectById(7L)).thenThrow(new IllegalStateException("push preparation failed"));
- org.junit.jupiter.api.Assertions.assertDoesNotThrow(() -> service.notifyAccepted(order()));
- }
- @Test
- void availableOrderRoutesNewFlashOrderToNearbyEligibleRiders() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- RiderPositionMapper positions = mock(RiderPositionMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class), positions));
- RiderPosition rider = new RiderPosition();
- rider.setRiderId(9L);
- rider.setCid("rider-cid");
- FlashDeliveryOrder order = order();
- order.setStatus("WAITING_ACCEPTANCE");
- order.setIsDisplay(true);
- order.setVehicleType(1);
- order.setDeliveryType("NORMAL");
- order.setPickupLongitude(new BigDecimal("121.5000"));
- order.setPickupLatitude(new BigDecimal("25.0300"));
- SysDictData distance = new SysDictData();
- distance.setDictValue("5");
- when(positions.getFlashRiderNotificationList(order.getPickupLongitude(), order.getPickupLatitude(),
- 1, "NORMAL", 5, 20)).thenReturn(List.of(rider));
- doNothing().when(service).sendAvailableOrderToRider(rider, order);
- try (MockedStatic<DictUtils> dictionary = org.mockito.Mockito.mockStatic(DictUtils.class)) {
- dictionary.when(() -> DictUtils.getDictCache("sys_qs_newtask_distance"))
- .thenReturn(List.of(distance));
- service.notifyAvailable(order);
- }
- verify(service).sendAvailableOrderToRider(rider, order);
- }
- @Test
- void nativeAvailableFlashOrderUsesRiderBatchAndUniKeepsPerRiderPath() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- RiderPositionMapper positions = mock(RiderPositionMapper.class);
- BusinessPushProperties properties = new BusinessPushProperties();
- NativeRiderBatchPushService batches = mock(NativeRiderBatchPushService.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class), positions, properties, batches));
- FlashDeliveryOrder order = order();
- order.setStatus("WAITING_ACCEPTANCE");
- order.setIsDisplay(true);
- order.setVehicleType(1);
- order.setDeliveryType("NORMAL");
- order.setPickupLongitude(new BigDecimal("121.5000"));
- order.setPickupLatitude(new BigDecimal("25.0300"));
- RiderPosition rider = new RiderPosition();
- rider.setRiderId(9L);
- List<RiderPosition> riders = List.of(rider);
- SysDictData distance = new SysDictData();
- distance.setDictValue("5");
- when(positions.getFlashRiderNotificationList(order.getPickupLongitude(), order.getPickupLatitude(),
- 1, "NORMAL", 5, 20)).thenReturn(riders);
- doNothing().when(service).sendAvailableOrderToRider(rider, order);
- try (MockedStatic<DictUtils> dictionary = org.mockito.Mockito.mockStatic(DictUtils.class)) {
- dictionary.when(() -> DictUtils.getDictCache("sys_qs_newtask_distance"))
- .thenReturn(List.of(distance));
- properties.setMode(BusinessPushProperties.Mode.NATIVE);
- service.notifyAvailable(order);
- verify(batches, timeout(3000)).sendNewOrder(org.mockito.ArgumentMatchers.eq(riders),
- org.mockito.ArgumentMatchers.eq("no.message.push.message"),
- org.mockito.ArgumentMatchers.eq("no.message.push.new.flash.order"),
- org.mockito.ArgumentMatchers.eq(OrderPushBodyDto.getJson(order.getOrderNo(),
- "WAITING_ACCEPTANCE", 0, 3)),
- org.mockito.ArgumentMatchers.eq(order.getOrderNo()), org.mockito.ArgumentMatchers.any());
- verify(service, never()).sendAvailableOrderToRider(rider, order);
- properties.setMode(BusinessPushProperties.Mode.UNI);
- service.notifyAvailable(order);
- verify(service).sendAvailableOrderToRider(rider, order);
- }
- }
- @Test
- void acceptedNotificationRoutesToSender() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class)));
- doNothing().when(service).sendToUser(any(), any(), anyString(), anyString(), any());
- InfoUser sender = new InfoUser();
- sender.setUserId(7L);
- FlashDeliveryOrder order = order();
- when(userMapper.selectById(7L)).thenReturn(sender);
- service.notifyAccepted(order);
- verify(service).sendToUser(same(sender), same(order), eq("ACCEPTED"),
- eq("no.message.push.delivery.personnel.receiving.order"), eq(UserNotificationScene.RIDER_ACCEPTED));
- }
- @Test
- void missingSenderSkipsNotification() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class)));
- when(userMapper.selectById(7L)).thenReturn(null);
- service.notifyAccepted(order());
- verify(service, never()).sendToUser(any(), any(), anyString(), anyString(), any());
- }
- @Test
- void pickedUpNotificationRoutesToSender() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class)));
- doNothing().when(service).sendToUser(any(), any(), anyString(), anyString(), any());
- InfoUser sender = new InfoUser();
- sender.setUserId(7L);
- FlashDeliveryOrder order = order();
- when(userMapper.selectById(7L)).thenReturn(sender);
- service.notifyPickedUp(order);
- verify(service).sendToUser(same(sender), same(order), eq("PICKED_UP"),
- eq("no.message.push.delivery.personnel.qspsz.order"), eq(UserNotificationScene.ORDER_DELIVERING));
- }
- @Test
- void deliveredNotificationRoutesToSender() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class)));
- doNothing().when(service).sendToUser(any(), any(), anyString(), anyString(), any());
- InfoUser sender = new InfoUser();
- sender.setUserId(7L);
- FlashDeliveryOrder order = order();
- when(userMapper.selectById(7L)).thenReturn(sender);
- service.notifyDelivered(order);
- verify(service).sendToUser(same(sender), same(order), eq("DELIVERED"),
- eq("no.message.push.delivery.personnel.qsysd.order"), eq(UserNotificationScene.ORDER_DELIVERED));
- }
- @Test
- void cancelledNotificationRoutesToAssignedRider() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class)));
- doNothing().when(service).sendToRider(any(), any(), anyString(), anyString());
- InfoUser rider = new InfoUser();
- rider.setUserId(9L);
- FlashDeliveryOrder order = order();
- order.setRiderId(9L);
- when(userMapper.selectById(9L)).thenReturn(rider);
- service.notifyCancelled(order);
- verify(service).sendToRider(same(rider), same(order), eq("CANCELLED"),
- eq("no.message.push.order.cancelled"));
- }
- @Test
- void cancelledNotificationSkipsWhenNoRiderAssigned() {
- InfoUserMapper userMapper = mock(InfoUserMapper.class);
- FlashDeliveryNotificationService service = spy(new FlashDeliveryNotificationService(
- userMapper, mock(PushEventService.class)));
- service.notifyCancelled(order());
- verify(service, never()).sendToRider(any(), any(), anyString(), anyString());
- verify(service, never()).sendToUser(any(), any(), anyString(), anyString(), any());
- }
- @Test
- void pickupStopProgressIsQueuedOnlyAfterCommitAndSentOnlyToOrderSender() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- FlashDeliveryOrder order = order();
- order.setStatus("ACCEPTED");
- order.setReceiverUserId(88L);
- order.setDeliveryPinCode("order-secret-pin");
- FlashDeliveryOrderStop stop = stop("PICKUP", 2);
- TransactionSynchronizationManager.initSynchronization();
- try {
- assertDoesNotThrow(() -> fixture.service.notifyPickupStop(order, stop, 1, 2));
- assertTrue(fixture.tasks.isEmpty());
- assertTrue(fixture.messages.isEmpty());
- assertTrue(fixture.lookedUpUsers.isEmpty());
- TransactionSynchronizationManager.getSynchronizations().forEach(TransactionSynchronization::afterCommit);
- assertEquals(1, fixture.tasks.size());
- assertTrue(fixture.messages.isEmpty());
- assertEquals(List.of(7L), fixture.lookedUpUsers);
- fixture.runTasks();
- } finally {
- TransactionSynchronizationManager.clearSynchronization();
- }
- assertEquals(1, fixture.messages.size());
- PushMessage message = fixture.messages.get(0);
- assertEquals(7L, message.getUserId());
- assertEquals("FL-20260813-021", message.getDdId());
- assertEquals(3, message.getPushType());
- JSONObject body = JSON.parseObject(message.getBody());
- assertEquals(Set.of("ddId", "state", "type", "pushType", "eventType", "stopId", "stopType",
- "stopOrder", "completedStopCount", "totalStopCount"), body.keySet());
- assertEquals("FL-20260813-021", body.getString("ddId"));
- assertEquals("ACCEPTED", body.getString("state"));
- assertEquals(-1, body.getIntValue("type"));
- assertEquals(3, body.getIntValue("pushType"));
- assertEquals("FLASH_PICKUP_STOP_COMPLETED", body.getString("eventType"));
- assertEquals(41L, body.getLong("stopId"));
- assertEquals("PICKUP", body.getString("stopType"));
- assertEquals(2, body.getIntValue("stopOrder"));
- assertEquals(1, body.getIntValue("completedStopCount"));
- assertEquals(2, body.getIntValue("totalStopCount"));
- assertFalse(message.getBody().contains("secret"));
- assertEquals(1, fixture.sentContents.size());
- assertTrue(fixture.sentContents.get(0).contains("stop 3"));
- assertTrue(fixture.sentContents.get(0).contains("1/2"));
- }
- }
- @Test
- void deliveryStopProgressKeepsRealPartialAndFinalOrderStatesWithoutWholeOrderScene() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- FlashDeliveryOrder partial = order();
- partial.setStatus("PICKED_UP");
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(partial, stop("DELIVERY", 3), 1, 2));
- FlashDeliveryOrder finished = order();
- finished.setStatus("DELIVERED");
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(finished, stop("DELIVERY", 4), 2, 2));
- fixture.runTasks();
- assertEquals(2, fixture.messages.size());
- List<JSONObject> bodies = fixture.messages.stream().map(message -> JSON.parseObject(message.getBody())).toList();
- assertEquals(List.of("PICKED_UP", "DELIVERED"), bodies.stream().map(body -> body.getString("state")).toList());
- assertEquals(List.of(1, 2), bodies.stream().map(body -> body.getIntValue("completedStopCount")).toList());
- for (JSONObject body : bodies) {
- assertEquals("FLASH_DELIVERY_STOP_COMPLETED", body.getString("eventType"));
- assertEquals("DELIVERY", body.getString("stopType"));
- assertEquals(2, body.getIntValue("totalStopCount"));
- assertFalse(body.containsKey("eventScene"));
- }
- assertEquals(List.of(7L, 7L), fixture.messages.stream().map(PushMessage::getUserId).toList());
- }
- }
- @Test
- void rollbackDoesNotQueueOrPersistEitherStopProgressNotification() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- TransactionSynchronizationManager.initSynchronization();
- try {
- assertDoesNotThrow(() -> fixture.service.notifyPickupStop(order(), stop("PICKUP", 0), 1, 2));
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(order(), stop("DELIVERY", 2), 1, 2));
- TransactionSynchronizationManager.getSynchronizations()
- .forEach(sync -> sync.afterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK));
- fixture.runTasks();
- assertTrue(fixture.lookedUpUsers.isEmpty());
- assertTrue(fixture.tasks.isEmpty());
- assertTrue(fixture.messages.isEmpty());
- assertTrue(fixture.sentContents.isEmpty());
- } finally {
- TransactionSynchronizationManager.clearSynchronization();
- }
- }
- }
- @Test
- void missingDeviceAddressStillPersistsStopProgressThroughRealPayPushEventPath() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- fixture.sender.setCid(null);
- for (BusinessPushProperties.Mode mode : BusinessPushProperties.Mode.values()) {
- fixture.properties.setMode(mode);
- assertDoesNotThrow(() -> fixture.service.notifyPickupStop(order(), stop("PICKUP", 0), 1, 2));
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(order(), stop("DELIVERY", 2), 1, 2));
- fixture.runTasks();
- }
- assertEquals(4, fixture.messages.size());
- assertTrue(fixture.messages.stream().allMatch(message -> Long.valueOf(7L).equals(message.getUserId())
- && Integer.valueOf(3).equals(message.getPushType())
- && "FL-20260813-021".equals(message.getDdId())));
- assertTrue(fixture.sentContents.isEmpty());
- }
- }
- @Test
- void stopProgressTextUsesSenderLocaleAndRouteNumberWithCompletedAndTotalCounts() {
- List<Locale> locales = List.of(Locale.ROOT, Locale.TAIWAN, Locale.SIMPLIFIED_CHINESE,
- Locale.US, Locale.forLanguageTag("th-TH"));
- List<String> pickupTexts = List.of("第 3 站已完成取貨,取貨進度 1/2", "第 3 站已完成取貨,取貨進度 1/2",
- "第 3 站已完成取货,取货进度 1/2", "Pickup completed at stop 3. Pickup progress: 1/2",
- "รับพัสดุที่จุด 3 แล้ว ความคืบหน้าการรับ: 1/2");
- List<String> deliveryTexts = List.of("第 5 站已送達,送達進度 2/2", "第 5 站已送達,送達進度 2/2",
- "第 5 站已送达,送达进度 2/2", "Delivery completed at stop 5. Delivery progress: 2/2",
- "ส่งพัสดุถึงจุด 5 แล้ว ความคืบหน้าการส่ง: 2/2");
- for (int i = 0; i < locales.size(); i++) {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- fixture.locale.when(() -> LocaleUtils.getUserLocale(7L)).thenReturn(locales.get(i));
- assertDoesNotThrow(() -> fixture.service.notifyPickupStop(order(), stop("PICKUP", 2), 1, 2));
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(order(), stop("DELIVERY", 4), 2, 2));
- fixture.runTasks();
- assertEquals(2, fixture.messages.size());
- assertTrue(fixture.messages.get(0).getContent().contains(pickupTexts.get(i)));
- assertTrue(fixture.messages.get(1).getContent().contains(deliveryTexts.get(i)));
- assertTrue(fixture.messages.stream().allMatch(message -> !message.getContent().contains("{0}")
- && message.getContent().contains("FL-20260813-021")));
- }
- }
- }
- @Test
- void stopNotificationLookupAndSchedulingFailuresDoNotEscapeBusinessOperation() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- when(fixture.users.selectById(7L)).thenThrow(new IllegalStateException("lookup failed"));
- assertDoesNotThrow(() -> fixture.service.notifyPickupStop(order(), stop("PICKUP", 0), 1, 2));
- assertTrue(fixture.tasks.isEmpty());
- org.mockito.Mockito.doReturn(fixture.sender).when(fixture.users).selectById(7L);
- doThrow(new IllegalStateException("executor unavailable")).when(fixture.manager).execute(any(TimerTask.class));
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(order(), stop("DELIVERY", 2), 1, 2));
- assertTrue(fixture.messages.isEmpty());
- }
- }
- @Test
- void stopNotificationPersistenceFailureIsContainedAndNextProgressStillSends() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- fixture.persistenceFailure = true;
- assertDoesNotThrow(() -> fixture.service.notifyPickupStop(order(), stop("PICKUP", 0), 1, 2));
- assertDoesNotThrow(fixture::runTasks);
- assertTrue(fixture.messages.isEmpty());
- fixture.persistenceFailure = false;
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(order(), stop("DELIVERY", 2), 1, 2));
- assertDoesNotThrow(fixture::runTasks);
- assertEquals(1, fixture.messages.size());
- assertEquals("FLASH_DELIVERY_STOP_COMPLETED", JSON.parseObject(fixture.messages.get(0).getBody()).getString("eventType"));
- }
- }
- @Test
- void externalStopPushFailureDoesNotLosePersistedProgressOrEscapeBusinessOperation() {
- try (StopNotificationFixture fixture = new StopNotificationFixture()) {
- doThrow(new IllegalStateException("network failure")).when(fixture.uni)
- .apppush(anyString(), anyString(), anyString(), anyString());
- assertDoesNotThrow(() -> fixture.service.notifyDeliveryStop(order(), stop("DELIVERY", 2), 1, 2));
- assertDoesNotThrow(fixture::runTasks);
- assertEquals(1, fixture.messages.size());
- assertEquals(7L, fixture.messages.get(0).getUserId());
- }
- }
- private static FlashDeliveryOrderStop stop(String type, int routeOrder) {
- FlashDeliveryOrderStop stop = new FlashDeliveryOrderStop();
- stop.setId(41L);
- stop.setOrderId(10L);
- stop.setStopType(type);
- stop.setStopOrder(routeOrder);
- stop.setReceiverUserId(99L);
- stop.setName("secret-contact");
- stop.setPhone("secret-phone");
- stop.setAddress("secret-address");
- stop.setDeliveryPinCode("secret-pin");
- return stop;
- }
- private static final class StopNotificationFixture implements AutoCloseable {
- final InfoUserMapper users = mock(InfoUserMapper.class);
- final InfoUser sender = new InfoUser();
- final PayPush uni = mock(PayPush.class);
- final BusinessPushProperties properties = new BusinessPushProperties();
- final List<Long> lookedUpUsers = new ArrayList<>();
- final List<TimerTask> tasks = new ArrayList<>();
- final List<PushMessage> messages = new ArrayList<>();
- final List<String> sentContents = new ArrayList<>();
- final MockedStatic<SpringUtils> spring;
- final MockedStatic<AsyncManager> async;
- final MockedStatic<LocaleUtils> locale;
- final AsyncManager manager;
- final FlashDeliveryNotificationService service;
- boolean persistenceFailure;
- StopNotificationFixture() {
- sender.setUserId(7L);
- sender.setCid("sender-cid");
- when(users.selectById(7L)).thenAnswer(call -> { lookedUpUsers.add(7L); return sender; });
- IInfoUserService currentUsers = mock(IInfoUserService.class);
- when(currentUsers.getById(7L)).thenReturn(sender);
- BusinessPushService router = new BusinessPushService(properties, currentUsers,
- mock(FcmPushSender.class), mock(ApnsPushSender.class), uni);
- IPushMessageService repository = mock(IPushMessageService.class);
- doAnswer(call -> {
- if (persistenceFailure) throw new IllegalStateException("message storage unavailable");
- messages.add(call.getArgument(0));
- return 1;
- }).when(repository).insertPushMessage(any(PushMessage.class));
- PushEventListener listener = new PushEventListener();
- ReflectionTestUtils.setField(listener, "pushMessageService", repository);
- PushEventService events = new PushEventService();
- ApplicationEventPublisher publisher = event -> listener.onApplicationEvent((PushEvent) event);
- ReflectionTestUtils.setField(events, "eventPublisher", publisher);
- ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
- messageSource.setBasename("i18n/messages");
- messageSource.setDefaultEncoding("UTF-8");
- messageSource.setFallbackToSystemLocale(false);
- spring = mockStatic(SpringUtils.class);
- spring.when(() -> SpringUtils.getBean("scheduledExecutorService")).thenReturn(mock(ScheduledExecutorService.class));
- spring.when(() -> SpringUtils.getBean(MessageSource.class)).thenReturn(messageSource);
- spring.when(() -> SpringUtils.getBean(BusinessPushService.class)).thenReturn(router);
- manager = mock(AsyncManager.class);
- doAnswer(call -> { tasks.add(call.getArgument(0)); return null; }).when(manager).execute(any(TimerTask.class));
- async = mockStatic(AsyncManager.class);
- async.when(AsyncManager::me).thenReturn(manager);
- locale = mockStatic(LocaleUtils.class);
- locale.when(() -> LocaleUtils.getUserLocale(7L)).thenReturn(Locale.US);
- doAnswer(call -> { sentContents.add(call.getArgument(2)); return null; }).when(uni)
- .apppush(eq("sender-cid"), anyString(), anyString(), anyString());
- service = new FlashDeliveryNotificationService(users, events);
- }
- void runTasks() {
- List<TimerTask> pending = new ArrayList<>(tasks);
- tasks.clear();
- pending.forEach(TimerTask::run);
- }
- @Override
- public void close() {
- locale.close();
- async.close();
- spring.close();
- }
- }
- private static FlashDeliveryOrder order() {
- FlashDeliveryOrder order = new FlashDeliveryOrder();
- order.setId(10L);
- order.setOrderNo("FL-20260813-021");
- order.setUserId(7L);
- return order;
- }
- }
|