This is an automated email from the ASF dual-hosted git repository. liuhongyu pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push: new df133fc950 fix: correct logic in local key validation in LocalDispatcherFilter (#6088) df133fc950 is described below commit df133fc9501c9c799d7116ee1fd4f44157f29f13 Author: Jast <745925...@qq.com> AuthorDate: Thu Aug 7 20:03:13 2025 +0800 fix: correct logic in local key validation in LocalDispatcherFilter (#6088) Co-authored-by: aias00 <liuhon...@apache.org> --- .../admin/service/AlertDispatchServiceTest.java | 526 +++++++++++++++++++++ 1 file changed, 526 insertions(+) diff --git a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AlertDispatchServiceTest.java b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AlertDispatchServiceTest.java new file mode 100644 index 0000000000..08db1d3b8e --- /dev/null +++ b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AlertDispatchServiceTest.java @@ -0,0 +1,526 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.shenyu.admin.service; + +import org.apache.shenyu.admin.mapper.AlertReceiverMapper; +import org.apache.shenyu.admin.service.impl.AlertDispatchServiceImpl; +import org.apache.shenyu.alert.AlertNotifyHandler; +import org.apache.shenyu.alert.exception.AlertNoticeException; +import org.apache.shenyu.alert.model.AlertReceiverDTO; +import org.apache.shenyu.common.dto.AlarmContent; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +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.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +/** + * Test case for AlertDispatchServiceImpl. + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class AlertDispatchServiceTest { + + private static final byte EMAIL_TYPE = 1; + + private static final byte WEBHOOK_TYPE = 2; + + private static final byte WECHAT_TYPE = 4; + + private static final byte UNKNOWN_TYPE = 99; + + private static final String TEST_NAMESPACE_ID = "test-namespace"; + + private static final String TEST_RECEIVER_ID = "test-receiver-id"; + + private static final String TEST_ALERT_TITLE = "Test Alert"; + + private static final String TEST_ALERT_CONTENT = "This is a test alert"; + + private AlertDispatchServiceImpl alertDispatchService; + + @Mock + private AlertNotifyHandler emailHandler; + + @Mock + private AlertNotifyHandler webhookHandler; + + @Mock + private AlertNotifyHandler wechatHandler; + + @Mock + private AlertReceiverMapper alertReceiverMapper; + + @BeforeEach + void setUp() { + when(emailHandler.type()).thenReturn(EMAIL_TYPE); + when(webhookHandler.type()).thenReturn(WEBHOOK_TYPE); + when(wechatHandler.type()).thenReturn(WECHAT_TYPE); + + List<AlertNotifyHandler> handlers = Arrays.asList(emailHandler, webhookHandler, wechatHandler); + + alertDispatchService = new AlertDispatchServiceImpl(handlers, alertReceiverMapper); + } + + @AfterEach + void tearDown() { + if (Objects.nonNull(alertDispatchService)) { + alertDispatchService.destroy(); + } + } + + @Test + void testConstructorInitializesCorrectly() { + + assertNotNull(alertDispatchService); + + // Verify thread pool is created + final ThreadPoolExecutor executor = getWorkerExecutor(); + assertNotNull(executor); + assertTrue(executor.getCorePoolSize() == 3); + assertTrue(executor.getMaximumPoolSize() == 3); + assertFalse(executor.isShutdown()); + } + + @Test + void testDispatchAlertSuccess() throws InterruptedException { + final AlertReceiverDTO receiver = createTestReceiver(EMAIL_TYPE, true, false); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(receiver)); + + final CountDownLatch latch = new CountDownLatch(1); + final AlarmContent alarmContent = createTestAlarmContent(); + + // Mock the handler to count down the latch when called + when(emailHandler.type()).thenReturn(EMAIL_TYPE); + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS), "Alert dispatch should complete within 5 seconds"); + + // Verify handler was called + Mockito.verify(emailHandler, times(1)).send(eq(receiver), eq(alarmContent)); + verify(alertReceiverMapper, times(1)).selectAll(); + } + + @Test + void testDispatchAlertWithNullContent() throws InterruptedException { + final AlarmContent nullContent = null; + + alertDispatchService.dispatchAlert(nullContent); + + // wait a bit to ensure task is processed + Thread.sleep(100); + + // Verify no handlers are called + verify(emailHandler, never()).send(any(), any()); + verify(webhookHandler, never()).send(any(), any()); + verify(wechatHandler, never()).send(any(), any()); + verify(alertReceiverMapper, never()).selectAll(); + } + + @Test + void testDispatchAlertWithMultipleReceivers() throws InterruptedException { + final AlertReceiverDTO emailReceiver = createTestReceiver(EMAIL_TYPE, true, false); + final AlertReceiverDTO webhookReceiver = createTestReceiver(WEBHOOK_TYPE, true, false); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(emailReceiver, webhookReceiver)); + + final CountDownLatch latch = new CountDownLatch(2); + final AlarmContent alarmContent = createTestAlarmContent(); + + // Mock handlers to count down the latch when called + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(webhookHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS), "Alert dispatch should complete within 5 seconds"); + + // Verify both handlers were called + verify(emailHandler, times(1)).send(eq(emailReceiver), eq(alarmContent)); + verify(webhookHandler, times(1)).send(eq(webhookReceiver), eq(alarmContent)); + } + + @Test + void testDispatchAlertWithHandlerException() throws InterruptedException { + final AlertReceiverDTO receiver = createTestReceiver(EMAIL_TYPE, true, false); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(receiver)); + + final CountDownLatch latch = new CountDownLatch(1); + final AlarmContent alarmContent = createTestAlarmContent(); + + doAnswer(invocation -> { + latch.countDown(); + throw new AlertNoticeException("Test exception"); + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS), "Alert dispatch should complete within 5 seconds"); + + verify(emailHandler, times(1)).send(eq(receiver), eq(alarmContent)); + } + + @Test + void testClearCache() throws Exception { + final AlertReceiverDTO receiver = createTestReceiver(EMAIL_TYPE, true, false); + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(receiver)); + + final CountDownLatch latch = new CountDownLatch(1); + final AlarmContent alarmContent = createTestAlarmContent(); + + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + assertTrue(latch.await(5, TimeUnit.SECONDS)); + + final AtomicReference<List<AlertReceiverDTO>> cacheRef = getAlertReceiverReference(); + assertNotNull(cacheRef.get()); + + alertDispatchService.clearCache(); + + assertNull(cacheRef.get()); + } + + @Test + void testSendNoticeMsgSuccess() { + final AlertReceiverDTO receiver = createTestReceiver(EMAIL_TYPE, true, false); + final AlarmContent alarmContent = createTestAlarmContent(); + + final boolean result = alertDispatchService.sendNoticeMsg(receiver, alarmContent); + + assertTrue(result); + verify(emailHandler, times(1)).send(eq(receiver), eq(alarmContent)); + } + + @Test + void testSendNoticeMsgWithNullReceiver() { + final AlertReceiverDTO nullReceiver = null; + final AlarmContent alarmContent = createTestAlarmContent(); + + final boolean result = alertDispatchService.sendNoticeMsg(nullReceiver, alarmContent); + + assertFalse(result); + verify(emailHandler, never()).send(any(), any()); + } + + @Test + void testSendNoticeMsgWithNullReceiverType() { + final AlertReceiverDTO receiver = createTestReceiver(null, true, false); + final AlarmContent alarmContent = createTestAlarmContent(); + + final boolean result = alertDispatchService.sendNoticeMsg(receiver, alarmContent); + + assertFalse(result); + verify(emailHandler, never()).send(any(), any()); + } + + @Test + void testSendNoticeMsgWithUnknownType() { + final AlertReceiverDTO receiver = createTestReceiver(UNKNOWN_TYPE, true, false); + final AlarmContent alarmContent = createTestAlarmContent(); + + final boolean result = alertDispatchService.sendNoticeMsg(receiver, alarmContent); + + assertFalse(result); + verify(emailHandler, never()).send(any(), any()); + verify(webhookHandler, never()).send(any(), any()); + verify(wechatHandler, never()).send(any(), any()); + } + + @Test + void testReceiverMatchingWithMatchAll() throws InterruptedException { + final AlarmContent alarmContent = createTestAlarmContent(); + final AlertReceiverDTO matchAllReceiver = createTestReceiver(EMAIL_TYPE, true, true); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(matchAllReceiver)); + + final CountDownLatch latch = new CountDownLatch(1); + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + verify(emailHandler, times(1)).send(eq(matchAllReceiver), eq(alarmContent)); + } + + @Test + void testReceiverMatchingWithDisabledReceiver() throws InterruptedException { + final AlarmContent alarmContent = createTestAlarmContent(); + final AlertReceiverDTO disabledReceiver = createTestReceiver(EMAIL_TYPE, false, false); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(disabledReceiver)); + + alertDispatchService.dispatchAlert(alarmContent); + + Thread.sleep(100); + + verify(emailHandler, never()).send(any(), any()); + } + + @Test + void testReceiverMatchingWithNamespaceId() throws InterruptedException { + final AlarmContent alarmContent = createTestAlarmContent(); + alarmContent.setNamespaceId(TEST_NAMESPACE_ID); + + final AlertReceiverDTO matchingReceiver = createTestReceiver(EMAIL_TYPE, true, false); + matchingReceiver.setNamespaceId(TEST_NAMESPACE_ID); + + final AlertReceiverDTO nonMatchingReceiver = createTestReceiver(WEBHOOK_TYPE, true, false); + nonMatchingReceiver.setNamespaceId("different-namespace"); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(matchingReceiver, nonMatchingReceiver)); + + final CountDownLatch latch = new CountDownLatch(1); + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + verify(emailHandler, times(1)).send(eq(matchingReceiver), eq(alarmContent)); + verify(webhookHandler, never()).send(any(), any()); + } + + @Test + void testReceiverMatchingWithLevels() throws InterruptedException { + final byte alertLevel = 1; + final AlarmContent alarmContent = createTestAlarmContent(); + alarmContent.setLevel(alertLevel); + + final AlertReceiverDTO matchingReceiver = createTestReceiver(EMAIL_TYPE, true, false); + matchingReceiver.setLevels(Arrays.asList((byte) 0, (byte) 1, (byte) 2)); + + final AlertReceiverDTO nonMatchingReceiver = createTestReceiver(WEBHOOK_TYPE, true, false); + nonMatchingReceiver.setLevels(Arrays.asList((byte) 0, (byte) 2)); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(matchingReceiver, nonMatchingReceiver)); + + final CountDownLatch latch = new CountDownLatch(1); + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + verify(emailHandler, times(1)).send(eq(matchingReceiver), eq(alarmContent)); + verify(webhookHandler, never()).send(any(), any()); + } + + @Test + void testReceiverMatchingWithLabels() throws InterruptedException { + final Map<String, String> alertLabels = new HashMap<>(); + alertLabels.put("service", "gateway"); + alertLabels.put("env", "prod"); + + final AlarmContent alarmContent = createTestAlarmContent(); + alarmContent.setLabels(alertLabels); + + final AlertReceiverDTO matchingReceiver = createTestReceiver(EMAIL_TYPE, true, false); + final Map<String, String> matchingLabels = new HashMap<>(); + matchingLabels.put("service", "gateway"); + matchingReceiver.setLabels(matchingLabels); + + final AlertReceiverDTO nonMatchingReceiver = createTestReceiver(WEBHOOK_TYPE, true, false); + final Map<String, String> nonMatchingLabels = new HashMap<>(); + nonMatchingLabels.put("service", "api"); + nonMatchingReceiver.setLabels(nonMatchingLabels); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(matchingReceiver, nonMatchingReceiver)); + + final CountDownLatch latch = new CountDownLatch(1); + doAnswer(invocation -> { + latch.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + alertDispatchService.dispatchAlert(alarmContent); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + verify(emailHandler, times(1)).send(eq(matchingReceiver), eq(alarmContent)); + verify(webhookHandler, never()).send(any(), any()); + } + + @Test + void testReceiverMatchingWithMissingAlertLabels() throws InterruptedException { + final AlarmContent alarmContent = createTestAlarmContent(); + alarmContent.setLabels(null); + + final AlertReceiverDTO receiverWithLabels = createTestReceiver(EMAIL_TYPE, true, false); + final Map<String, String> requiredLabels = new HashMap<>(); + requiredLabels.put("service", "gateway"); + receiverWithLabels.setLabels(requiredLabels); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(receiverWithLabels)); + + alertDispatchService.dispatchAlert(alarmContent); + + Thread.sleep(100); + + // Verify receiver with required labels is not matched when alert has no labels + verify(emailHandler, never()).send(any(), any()); + } + + @Test + void testReceiverCacheUsage() throws InterruptedException { + final AlarmContent alarmContent = createTestAlarmContent(); + final AlertReceiverDTO receiver = createTestReceiver(EMAIL_TYPE, true, false); + + when(alertReceiverMapper.selectAll()).thenReturn(Arrays.asList(receiver)); + + final CountDownLatch latch1 = new CountDownLatch(1); + final CountDownLatch latch2 = new CountDownLatch(1); + + doAnswer(invocation -> { + latch1.countDown(); + return null; + }).doAnswer(invocation -> { + latch2.countDown(); + return null; + }).when(emailHandler).send(any(AlertReceiverDTO.class), any(AlarmContent.class)); + + // dispatch first alert + alertDispatchService.dispatchAlert(alarmContent); + assertTrue(latch1.await(5, TimeUnit.SECONDS)); + + // dispatch second alert + alertDispatchService.dispatchAlert(alarmContent); + assertTrue(latch2.await(5, TimeUnit.SECONDS)); + + // verify mapper is called only once (cache is used for second call) + verify(alertReceiverMapper, times(1)).selectAll(); + verify(emailHandler, times(2)).send(eq(receiver), eq(alarmContent)); + } + + @Test + void testDestroy() { + final ThreadPoolExecutor executor = getWorkerExecutor(); + assertFalse(executor.isShutdown()); + + alertDispatchService.destroy(); + + assertTrue(executor.isShutdown()); + } + + @Test + void testDestroyWithNullExecutor() throws Exception { + final Field executorField = AlertDispatchServiceImpl.class.getDeclaredField("workerExecutor"); + executorField.setAccessible(true); + executorField.set(alertDispatchService, null); + + alertDispatchService.destroy(); + } + + private AlarmContent createTestAlarmContent() { + return new AlarmContent.Builder() + .title(TEST_ALERT_TITLE) + .content(TEST_ALERT_CONTENT) + .level((byte) 1) + .namespaceId(TEST_NAMESPACE_ID) + .dateCreated(new Date()) + .dateUpdated(new Date()) + .build(); + } + + private AlertReceiverDTO createTestReceiver(final Byte type, final boolean enabled, final boolean matchAll) { + AlertReceiverDTO receiver = new AlertReceiverDTO(); + receiver.setId(TEST_RECEIVER_ID); + receiver.setName("Test Receiver"); + receiver.setType(type); + receiver.setEnable(enabled); + receiver.setMatchAll(matchAll); + receiver.setEmail("t...@example.com"); + receiver.setDateCreated(new Date()); + receiver.setDateUpdated(new Date()); + return receiver; + } + + @SuppressWarnings("unchecked") + private AtomicReference<List<AlertReceiverDTO>> getAlertReceiverReference() { + try { + Field field = AlertDispatchServiceImpl.class.getDeclaredField("alertReceiverReference"); + field.setAccessible(true); + return (AtomicReference<List<AlertReceiverDTO>>) field.get(alertDispatchService); + } catch (Exception e) { + throw new RuntimeException("Failed to get alertReceiverReference field", e); + } + } + + private ThreadPoolExecutor getWorkerExecutor() { + try { + Field field = AlertDispatchServiceImpl.class.getDeclaredField("workerExecutor"); + field.setAccessible(true); + return (ThreadPoolExecutor) field.get(alertDispatchService); + } catch (Exception e) { + throw new RuntimeException("Failed to get workerExecutor field", e); + } + } +} +