|
|
@@ -0,0 +1,179 @@
|
|
|
+package com.ruoyi.app.order;
|
|
|
+
|
|
|
+import com.ruoyi.app.user.MerchantTokenSessionService;
|
|
|
+import com.ruoyi.app.utils.event.PushEventService;
|
|
|
+import com.ruoyi.system.domain.InfoUser;
|
|
|
+import com.ruoyi.system.domain.PosStore;
|
|
|
+import com.ruoyi.system.mapper.MerchantSubaccountStoreMapper;
|
|
|
+import com.ruoyi.system.mapper.PosStoreMapper;
|
|
|
+import com.ruoyi.system.service.IInfoUserService;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.util.List;
|
|
|
+
|
|
|
+import static org.mockito.ArgumentMatchers.anyLong;
|
|
|
+import static org.mockito.ArgumentMatchers.anyString;
|
|
|
+import static org.mockito.Mockito.doAnswer;
|
|
|
+import static org.mockito.Mockito.doNothing;
|
|
|
+import static org.mockito.Mockito.doThrow;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.never;
|
|
|
+import static org.mockito.Mockito.spy;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
+import static org.mockito.Mockito.verify;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+class MerchantNotificationRouterTest {
|
|
|
+ private MerchantSubaccountStoreMapper relations;
|
|
|
+ private PosStoreMapper stores;
|
|
|
+ private IInfoUserService users;
|
|
|
+ private MerchantTokenSessionService sessions;
|
|
|
+ private PushEventService events;
|
|
|
+ private MerchantNotificationRouter router;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ relations = mock(MerchantSubaccountStoreMapper.class);
|
|
|
+ stores = mock(PosStoreMapper.class);
|
|
|
+ users = mock(IInfoUserService.class);
|
|
|
+ sessions = mock(MerchantTokenSessionService.class);
|
|
|
+ events = mock(PushEventService.class);
|
|
|
+ router = spy(new MerchantNotificationRouter(relations, stores, users, sessions, events));
|
|
|
+ doAnswer(invocation -> invocation.getArgument(0)).when(router).localize(anyString(), anyLong());
|
|
|
+ doNothing().when(router).sendExternal(anyString(), anyString(), anyString(), anyString());
|
|
|
+ when(stores.selectPosStoreById(9L)).thenReturn(store(9, 101L));
|
|
|
+ when(users.getById(101L)).thenReturn(owner(101L));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void sendsMessageToEveryOnlineSubaccountWithoutDuplicatingOwner() {
|
|
|
+ InfoUser first = subaccount(501L, "cid-a");
|
|
|
+ InfoUser second = subaccount(502L, "cid-b");
|
|
|
+ when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(first, second));
|
|
|
+ when(sessions.hasAppSession(501L)).thenReturn(true);
|
|
|
+ when(sessions.hasAppSession(502L)).thenReturn(true);
|
|
|
+
|
|
|
+ router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(events).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events, never()).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(router).sendExternal("cid-a", "title", "content,NO:DD-1", "body");
|
|
|
+ verify(router).sendExternal("cid-b", "title", "content,NO:DD-1", "body");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void storesOneMessagePerAccountButPushesSharedCidOnlyOnce() {
|
|
|
+ InfoUser first = subaccount(501L, "shared-cid");
|
|
|
+ InfoUser second = subaccount(502L, "shared-cid");
|
|
|
+ when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(first, second));
|
|
|
+ when(sessions.hasAppSession(anyLong())).thenReturn(true);
|
|
|
+
|
|
|
+ router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(events).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(router, times(1)).sendExternal("shared-cid", "title", "content,NO:DD-1", "body");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void fallsBackToOwnerWhenNoSubaccountHasAppSessionAndCid() {
|
|
|
+ InfoUser offline = subaccount(501L, "cid-a");
|
|
|
+ InfoUser missingCid = subaccount(502L, null);
|
|
|
+ when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(offline, missingCid));
|
|
|
+
|
|
|
+ router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(events).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events, never()).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void disabledSubaccountAndDisabledOwnerDoNotReceiveNotification() {
|
|
|
+ InfoUser disabledSubaccount = subaccount(501L, "cid-a");
|
|
|
+ disabledSubaccount.setStatus("1");
|
|
|
+ InfoUser disabledOwner = owner(101L);
|
|
|
+ disabledOwner.setStatus("1");
|
|
|
+ when(users.getById(101L)).thenReturn(disabledOwner);
|
|
|
+ when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(disabledSubaccount));
|
|
|
+ when(sessions.hasAppSession(501L)).thenReturn(true);
|
|
|
+
|
|
|
+ router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(events, never()).PublisherEvent(anyLong(), anyString(), anyString(), anyString());
|
|
|
+ verify(relations, never()).selectEligibleSubaccountsByStoreId(9L);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void platformOrOwnerDisabledSubaccountsAreExcludedAndOwnerReceivesFallback() {
|
|
|
+ InfoUser platformDisabled = subaccount(501L, "cid-a");
|
|
|
+ platformDisabled.setStatus("1");
|
|
|
+ InfoUser ownerDisabled = subaccount(502L, "cid-b");
|
|
|
+ ownerDisabled.setSubaccountStatus("1");
|
|
|
+ when(relations.selectEligibleSubaccountsByStoreId(9L))
|
|
|
+ .thenReturn(List.of(platformDisabled, ownerDisabled));
|
|
|
+ when(sessions.hasAppSession(anyLong())).thenReturn(true);
|
|
|
+
|
|
|
+ router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(events).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events, never()).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events, never()).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void oneExternalFailureDoesNotBlockOtherMessagesOrPushes() {
|
|
|
+ InfoUser first = subaccount(501L, "cid-a");
|
|
|
+ InfoUser second = subaccount(502L, "cid-b");
|
|
|
+ when(relations.selectEligibleSubaccountsByStoreId(9L)).thenReturn(List.of(first, second));
|
|
|
+ when(sessions.hasAppSession(anyLong())).thenReturn(true);
|
|
|
+ doThrow(new IllegalStateException("push failed"))
|
|
|
+ .when(router).sendExternal("cid-a", "title", "content,NO:DD-1", "body");
|
|
|
+
|
|
|
+ router.sendStoreNotification(9L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(events).PublisherEvent(501L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(events).PublisherEvent(502L, "title", "content,NO:DD-1", "body");
|
|
|
+ verify(router).sendExternal("cid-b", "title", "content,NO:DD-1", "body");
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void missingStoreRoutesOnlyToProvidedOwnerFallback() {
|
|
|
+ when(stores.selectPosStoreById(99L)).thenReturn(null);
|
|
|
+
|
|
|
+ router.sendStoreNotification(99L, 101L, "title", "content", "body", "DD-1");
|
|
|
+
|
|
|
+ verify(relations, never()).selectEligibleSubaccountsByStoreId(99L);
|
|
|
+ verify(events).PublisherEvent(101L, "title", "content,NO:DD-1", "body");
|
|
|
+ }
|
|
|
+
|
|
|
+ private static InfoUser owner(Long id) {
|
|
|
+ InfoUser user = new InfoUser();
|
|
|
+ user.setUserId(id);
|
|
|
+ user.setUserType("1");
|
|
|
+ user.setStatus("0");
|
|
|
+ user.setDelFlag("0");
|
|
|
+ user.setCid("owner-cid");
|
|
|
+ return user;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static InfoUser subaccount(Long id, String cid) {
|
|
|
+ InfoUser user = new InfoUser();
|
|
|
+ user.setUserId(id);
|
|
|
+ user.setUserType("5");
|
|
|
+ user.setStatus("0");
|
|
|
+ user.setSubaccountStatus("0");
|
|
|
+ user.setDelFlag("0");
|
|
|
+ user.setCid(cid);
|
|
|
+ return user;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static PosStore store(Integer id, Long ownerId) {
|
|
|
+ PosStore store = new PosStore();
|
|
|
+ store.setId(id);
|
|
|
+ store.setUserId(ownerId);
|
|
|
+ store.setDelFlag("0");
|
|
|
+ return store;
|
|
|
+ }
|
|
|
+}
|