This is an automated email from the ASF dual-hosted git repository.
Aias00 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 7b0eaea89c test(admin): add coverage for service implementations
(#7548)
7b0eaea89c is described below
commit 7b0eaea89c1dd17d2161f2f2d9c89a7c8cadb613
Author: Southern <[email protected]>
AuthorDate: Mon Oct 12 09:11:41 2026 +0800
test(admin): add coverage for service implementations (#7548)
---
.../service/AiProxyConnectionServiceImplTest.java | 110 ++++++++
.../admin/service/DiscoveryServiceImplTest.java | 294 ++++++++++++++++++++-
.../service/NamespaceUserServiceImplTest.java | 71 +++++
.../service/OperationRecordLogServiceImplTest.java | 111 ++++++++
.../admin/service/RegistryServiceImplTest.java | 145 ++++++++++
.../admin/service/ScalePolicyServiceImplTest.java | 105 ++++++++
.../shenyu/admin/service/ScaleRuleServiceTest.java | 113 +++++++-
.../manager/RegisterApiDocServiceImplTest.java | 162 ++++++++++++
.../ShenyuClientRegisterMcpServiceImplTest.java | 172 ++++++++++++
.../plugin/base/alert/AlarmServiceImplTest.java | 108 ++++++++
10 files changed, 1380 insertions(+), 11 deletions(-)
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AiProxyConnectionServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AiProxyConnectionServiceImplTest.java
new file mode 100644
index 0000000000..ce26988396
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/AiProxyConnectionServiceImplTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.listener.DataChangedEvent;
+import org.apache.shenyu.admin.mapper.AiProxyApiKeyMapper;
+import org.apache.shenyu.admin.model.entity.ProxyApiKeyDO;
+import org.apache.shenyu.admin.service.impl.AiProxyConnectionServiceImpl;
+import org.apache.shenyu.admin.service.support.AiProxyRealKeyResolver;
+import org.apache.shenyu.common.dto.ProxyApiKeyData;
+import org.apache.shenyu.common.enums.ConfigGroupEnum;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link AiProxyConnectionServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class AiProxyConnectionServiceImplTest {
+
+ @Mock
+ private AiProxyRealKeyResolver resolver;
+
+ @Mock
+ private AiProxyApiKeyMapper mapper;
+
+ @Mock
+ private org.springframework.context.ApplicationEventPublisher publisher;
+
+ @InjectMocks
+ private AiProxyConnectionServiceImpl service;
+
+ @Test
+ public void testRefreshWithNoKeysOnlyInvalidates() {
+
when(mapper.selectBySelectorId("s1")).thenReturn(Collections.emptyList());
+ service.refreshApiKeysBySelectorId("s1");
+ verify(resolver).invalidate("s1");
+ verifyNoInteractions(publisher);
+ }
+
+ @Test
+ public void testRefreshPublishesResolvedData() {
+ ProxyApiKeyDO key = new ProxyApiKeyDO();
+ key.setSelectorId("s1");
+ key.setNamespaceId(null);
+ key.setProxyApiKey("proxy");
+ key.setEnabled(Boolean.TRUE);
+ key.setDescription("desc");
+
when(mapper.selectBySelectorId("s1")).thenReturn(Collections.singletonList(key));
+ when(resolver.resolveRealKey("s1")).thenReturn(Optional.of("real"));
+ service.refreshApiKeysBySelectorId("s1");
+ ArgumentCaptor<DataChangedEvent> captor =
ArgumentCaptor.forClass(DataChangedEvent.class);
+ verify(publisher).publishEvent(captor.capture());
+ ProxyApiKeyData data = (ProxyApiKeyData) ((java.util.List<?>)
captor.getValue().getSource()).get(0);
+ assertEquals("real", data.getRealApiKey());
+ assertEquals("proxy", data.getProxyApiKey());
+ assertEquals(Boolean.TRUE, data.getEnabled());
+ assertEquals("s1", data.getSelectorId());
+ assertEquals("desc", data.getDescription());
+
assertEquals(org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID,
data.getNamespaceId());
+ assertEquals(ConfigGroupEnum.AI_PROXY_API_KEY,
captor.getValue().getGroupKey());
+ assertEquals(DataEventTypeEnum.UPDATE,
ReflectionTestUtils.getField(captor.getValue(), "eventType"));
+ }
+
+ @Test
+ public void testRefreshHandlesMissingRealKeyAndDisabledMapping() {
+ ProxyApiKeyDO key = new ProxyApiKeyDO();
+ key.setSelectorId("s1");
+ key.setNamespaceId("custom");
+ key.setProxyApiKey("proxy");
+ key.setEnabled(Boolean.FALSE);
+
when(mapper.selectBySelectorId("s1")).thenReturn(Collections.singletonList(key));
+ when(resolver.resolveRealKey("s1")).thenReturn(Optional.empty());
+ service.refreshApiKeysBySelectorId("s1");
+ ArgumentCaptor<DataChangedEvent> captor =
ArgumentCaptor.forClass(DataChangedEvent.class);
+ verify(publisher).publishEvent(captor.capture());
+ ProxyApiKeyData data = (ProxyApiKeyData) ((java.util.List<?>)
captor.getValue().getSource()).get(0);
+ assertNull(data.getRealApiKey());
+ assertEquals(Boolean.FALSE, data.getEnabled());
+ assertEquals("custom", data.getNamespaceId());
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
index b640bcf47a..b803bef72e 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/DiscoveryServiceImplTest.java
@@ -27,39 +27,50 @@ import org.apache.shenyu.admin.mapper.SelectorMapper;
import org.apache.shenyu.admin.model.dto.DiscoveryDTO;
import org.apache.shenyu.admin.model.dto.DiscoveryHandlerDTO;
import org.apache.shenyu.admin.model.dto.DiscoveryRelDTO;
+import org.apache.shenyu.admin.model.dto.ProxySelectorDTO;
import org.apache.shenyu.admin.model.entity.DiscoveryDO;
import org.apache.shenyu.admin.model.entity.DiscoveryRelDO;
import org.apache.shenyu.admin.service.configs.ConfigsImportContext;
+import org.apache.shenyu.admin.model.entity.DiscoveryHandlerDO;
+import org.apache.shenyu.admin.model.entity.ProxySelectorDO;
import org.apache.shenyu.admin.model.entity.SelectorDO;
-import org.apache.shenyu.register.common.dto.DiscoveryConfigRegisterDTO;
-import org.springframework.transaction.PlatformTransactionManager;
-import org.springframework.transaction.support.SimpleTransactionStatus;
-import org.springframework.transaction.support.TransactionSynchronization;
-import
org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.apache.shenyu.admin.model.result.ConfigImportResult;
+import org.apache.shenyu.admin.model.vo.DiscoveryVO;
import org.apache.shenyu.admin.service.impl.DiscoveryServiceImpl;
import org.apache.shenyu.admin.utils.ShenyuResultMessage;
import org.apache.shenyu.common.exception.ShenyuException;
+import org.apache.shenyu.register.common.dto.DiscoveryConfigRegisterDTO;
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.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.junit.jupiter.MockitoSettings;
+import org.mockito.Mock;
import org.mockito.quality.Strictness;
+import org.springframework.transaction.PlatformTransactionManager;
+import org.springframework.transaction.support.SimpleTransactionStatus;
+import org.springframework.transaction.support.TransactionSynchronization;
+import
org.springframework.transaction.support.TransactionSynchronizationManager;
import java.util.Collections;
+import java.util.List;
import static
org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID;
import static org.junit.jupiter.api.Assertions.assertEquals;
+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.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
-import org.mockito.ArgumentCaptor;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
-import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.when;
/**
* Test cases for DiscoveryServiceImpl.
@@ -139,6 +150,37 @@ public final class DiscoveryServiceImplTest {
verify(discoveryProcessor).createProxySelector(any(), any());
}
+ @Test
+ public void testBindingRetriesAndFailsWhenSelectorIsMissing() {
+ DiscoveryConfigRegisterDTO config = bindingConfig();
+
given(selectorService.findByNameAndPluginNameAndNamespaceIdForUpdate("test",
"divide", SYS_DEFAULT_NAMESPACE_ID))
+ .willReturn(null);
+ assertThrows(ShenyuException.class, () ->
discoveryService.registerDiscoveryConfig(config));
+ verify(selectorService, org.mockito.Mockito.times(3))
+ .findByNameAndPluginNameAndNamespaceIdForUpdate("test",
"divide", SYS_DEFAULT_NAMESPACE_ID);
+ verifyNoInteractions(discoveryMapper, discoveryHandlerMapper,
discoveryRelMapper, discoveryProcessor);
+ }
+
+ @Test
+ public void testBindingReusesExistingDiscoveryAndHandler() {
+ SelectorDO selector = new SelectorDO();
+ selector.setId("selector");
+ selector.setSelectorName("test");
+ selector.setNamespaceId(SYS_DEFAULT_NAMESPACE_ID);
+
given(selectorService.findByNameAndPluginNameAndNamespaceIdForUpdate("test",
"divide", SYS_DEFAULT_NAMESPACE_ID))
+ .willReturn(selector);
+ DiscoveryDO existing =
DiscoveryDO.builder().id("discovery").discoveryType("local").build();
+ DiscoveryHandlerDO handler =
DiscoveryHandlerDO.builder().id("handler").discoveryId("discovery").build();
+
given(discoveryMapper.selectByPluginNameAndLevelAndNamespaceIdAndType(anyString(),
anyString(), anyString(), anyString()))
+ .willReturn(existing);
+
given(discoveryHandlerMapper.selectBySelectorId("selector")).willReturn(handler);
+ discoveryService.registerDiscoveryConfig(bindingConfig());
+ verify(discoveryMapper, never()).insertSelective(any());
+ verify(discoveryHandlerMapper, never()).insertSelective(any());
+ verify(discoveryRelMapper, never()).insertSelective(any());
+ verify(discoveryProcessor).createDiscovery(existing);
+ }
+
private DiscoveryConfigRegisterDTO bindingConfig() {
SelectorDO selector = new SelectorDO();
selector.setId("selector");
@@ -278,4 +320,238 @@ public final class DiscoveryServiceImplTest {
assertEquals("", relCaptor.getValue().getSelectorId());
assertEquals("proxy-selector-new",
relCaptor.getValue().getProxySelectorId());
}
+
+ @Test
+ public void testDeleteWithHandlerBound() {
+ given(discoveryHandlerMapper.selectByDiscoveryId("discovery-1"))
+
.willReturn(Collections.singletonList(DiscoveryHandlerDO.builder().id("handler").build()));
+ assertThrows(ShenyuException.class, () ->
discoveryService.delete("discovery-1", SYS_DEFAULT_NAMESPACE_ID));
+ verify(discoveryMapper, never()).selectById("discovery-1");
+ }
+
+ @Test
+ public void testTypeCreateUpdateAndDiscoveryLookup() {
+
assertEquals(org.apache.shenyu.admin.model.enums.DiscoveryTypeEnum.types(),
discoveryService.typeEnums());
+ DiscoveryDTO dto = new DiscoveryDTO();
+ dto.setName("d");
+ dto.setPluginName("divide");
+ dto.setType("local");
+ dto.setLevel("plugin");
+ dto.setNamespaceId(SYS_DEFAULT_NAMESPACE_ID);
+ when(discoveryMapper.insert(any())).thenReturn(1);
+ assertEquals("local",
discoveryService.createOrUpdate(dto).getDiscoveryType());
+ dto.setId("id");
+ when(discoveryMapper.updateSelective(any())).thenReturn(1);
+ assertEquals("local",
discoveryService.createOrUpdate(dto).getDiscoveryType());
+ dto.setId(null);
+ when(discoveryMapper.insert(any())).thenReturn(0);
+ assertNull(discoveryService.createOrUpdate(dto));
+ discoveryService.discovery("divide", "plugin",
SYS_DEFAULT_NAMESPACE_ID);
+
verify(discoveryMapper).selectByPluginNameAndLevelAndNamespaceId("divide",
"plugin", SYS_DEFAULT_NAMESPACE_ID);
+ }
+
+ @Test
+ public void testSyncAndListData() {
+ SelectorDO selector = new SelectorDO();
+ selector.setId("s");
+ selector.setSelectorName("selector");
+ selector.setNamespaceId(SYS_DEFAULT_NAMESPACE_ID);
+
when(proxySelectorMapper.selectByDiscoveryId("d")).thenReturn(Collections.emptyList());
+
when(selectorMapper.selectByDiscoveryId("d")).thenReturn(Collections.singletonList(selector));
+
when(discoveryHandlerMapper.selectBySelectorId("s")).thenReturn(DiscoveryHandlerDO.builder().id("h").discoveryId("d").build());
+ DiscoveryDO discovery =
DiscoveryDO.builder().id("d").pluginName("divide").discoveryType("local").namespaceId(SYS_DEFAULT_NAMESPACE_ID).build();
+ discoveryService.syncData(Collections.singletonList(discovery));
+ verify(discoveryProcessor).createDiscovery(discovery);
+ verify(discoveryProcessor).fetchAll(any(), any());
+
when(discoveryMapper.selectAll()).thenReturn(Collections.singletonList(discovery));
+
when(discoveryHandlerMapper.selectAll()).thenReturn(Collections.emptyList());
+
when(discoveryRelMapper.selectAll()).thenReturn(Collections.emptyList());
+ assertEquals(1, discoveryService.listAllData().size());
+
when(discoveryMapper.selectAllByNamespaceId(SYS_DEFAULT_NAMESPACE_ID)).thenReturn(Collections.emptyList());
+ assertEquals(0,
discoveryService.listAllDataByNamespaceId(SYS_DEFAULT_NAMESPACE_ID).size());
+ discoveryService.syncData();
+ discoveryService.syncDataByNamespaceId(SYS_DEFAULT_NAMESPACE_ID);
+ }
+
+ @Test
+ public void testSyncDataWithProxySelector() {
+ final DiscoveryDO discovery =
DiscoveryDO.builder().id("d").pluginName("divide").discoveryType("local").build();
+ ProxySelectorDO proxySelector =
ProxySelectorDO.builder().id("proxy").name("proxy-name")
+
.pluginName("divide").namespaceId(SYS_DEFAULT_NAMESPACE_ID).build();
+ DiscoveryHandlerDO handler =
DiscoveryHandlerDO.builder().id("handler").discoveryId("d").build();
+
when(proxySelectorMapper.selectByDiscoveryId("d")).thenReturn(Collections.singletonList(proxySelector));
+
when(discoveryHandlerMapper.selectByProxySelectorId("proxy")).thenReturn(handler);
+
when(selectorMapper.selectByDiscoveryId("d")).thenReturn(Collections.emptyList());
+ discoveryService.syncData(Collections.singletonList(discovery));
+ ArgumentCaptor<DiscoveryHandlerDTO> handlerCaptor =
ArgumentCaptor.forClass(DiscoveryHandlerDTO.class);
+ ArgumentCaptor<ProxySelectorDTO> selectorCaptor =
ArgumentCaptor.forClass(ProxySelectorDTO.class);
+
verify(discoveryProcessor).createProxySelector(handlerCaptor.capture(),
selectorCaptor.capture());
+ assertEquals("handler", handlerCaptor.getValue().getId());
+ assertEquals("proxy", selectorCaptor.getValue().getId());
+ assertEquals("proxy-name", selectorCaptor.getValue().getName());
+ assertEquals(SYS_DEFAULT_NAMESPACE_ID,
selectorCaptor.getValue().getNamespaceId());
+ verify(discoveryProcessor).fetchAll(argThat(dto ->
"handler".equals(dto.getId())), eq(selectorCaptor.getValue()));
+ }
+
+ @Test
+ public void testListDataWithRelationsAndFindHandler() {
+ DiscoveryDO discovery =
DiscoveryDO.builder().id("d").discoveryName("name").discoveryType("local").build();
+ DiscoveryHandlerDO handler =
DiscoveryHandlerDO.builder().id("h").discoveryId("d").build();
+ DiscoveryRelDO relation =
DiscoveryRelDO.builder().id("r").discoveryHandlerId("h").pluginName("divide").build();
+
when(discoveryMapper.selectAll()).thenReturn(Collections.singletonList(discovery));
+
when(discoveryHandlerMapper.selectAll()).thenReturn(Collections.singletonList(handler));
+
when(discoveryRelMapper.selectAll()).thenReturn(Collections.singletonList(relation));
+ List<DiscoveryVO> all = discoveryService.listAllData();
+ assertEquals("h", all.get(0).getDiscoveryHandler().getId());
+ assertEquals("r", all.get(0).getDiscoveryRel().getId());
+
when(discoveryHandlerMapper.selectBySelectorId("selector")).thenReturn(handler);
+ assertEquals("h",
discoveryService.findDiscoveryHandlerBySelectorId("selector").getId());
+ }
+
+ @Test
+ public void testListDataByNamespaceWithHandlerAndRelation() {
+ DiscoveryDO discovery =
DiscoveryDO.builder().id("d").discoveryType("local").build();
+ DiscoveryHandlerDO handler =
DiscoveryHandlerDO.builder().id("h").discoveryId("d").build();
+ DiscoveryRelDO relation =
DiscoveryRelDO.builder().id("r").discoveryHandlerId("h").build();
+
when(discoveryMapper.selectAllByNamespaceId("ns")).thenReturn(Collections.singletonList(discovery));
+
when(discoveryHandlerMapper.selectByDiscoveryIds(any())).thenReturn(Collections.singletonList(handler));
+
when(discoveryRelMapper.selectAll()).thenReturn(Collections.singletonList(relation));
+ assertEquals("r",
discoveryService.listAllDataByNamespaceId("ns").get(0).getDiscoveryRel().getId());
+ }
+
+ @Test
+ public void testRegisterDefaultAndImport() {
+ when(discoveryHandlerMapper.selectBySelectorId("s")).thenReturn(null);
+ String handlerId = discoveryService.registerDefaultDiscovery("s",
"divide", SYS_DEFAULT_NAMESPACE_ID);
+ org.junit.jupiter.api.Assertions.assertNotNull(handlerId);
+ verify(discoveryMapper).insertSelective(any());
+
when(discoveryHandlerMapper.selectBySelectorId("existing")).thenReturn(DiscoveryHandlerDO.builder().id("h").build());
+ assertEquals("h",
discoveryService.registerDefaultDiscovery("existing", "divide",
SYS_DEFAULT_NAMESPACE_ID));
+ assertEquals(0,
discoveryService.importData(Collections.emptyList()).getSuccessCount());
+ DiscoveryDTO dto = new DiscoveryDTO();
+ dto.setName("new");
+ dto.setPluginName("divide");
+ dto.setType("local");
+ dto.setLevel("plugin");
+ when(discoveryMapper.selectAll()).thenReturn(Collections.emptyList());
+ when(discoveryMapper.insert(any())).thenReturn(1);
+ ConfigImportResult result =
discoveryService.importData(Collections.singletonList(dto));
+ assertEquals(1, result.getSuccessCount());
+ DiscoveryDO existing =
DiscoveryDO.builder().pluginName("divide").discoveryName("duplicate").build();
+
when(discoveryMapper.selectAll()).thenReturn(Collections.singletonList(existing));
+ DiscoveryDTO duplicate = new DiscoveryDTO();
+ duplicate.setPluginName("divide");
+ duplicate.setName("duplicate");
+ assertEquals(0,
discoveryService.importData(Collections.singletonList(duplicate)).getSuccessCount());
+ ConfigsImportContext context = new ConfigsImportContext();
+
when(discoveryMapper.selectAllByNamespaceId("ns")).thenReturn(Collections.emptyList());
+ assertEquals(1, discoveryService.importData("ns",
Collections.singletonList(dto), context).getSuccessCount());
+ }
+
+ @Test
+ public void testImportDataWithHandlerAndRelation() {
+ DiscoveryDTO dto = new DiscoveryDTO();
+ dto.setName("new");
+ dto.setPluginName("divide");
+ dto.setType("local");
+ DiscoveryHandlerDTO handler = new DiscoveryHandlerDTO();
+ handler.setId("handler");
+ handler.setHandler("{}");
+ dto.setDiscoveryHandler(handler);
+ DiscoveryRelDTO relation = new DiscoveryRelDTO();
+ relation.setId("relation");
+ relation.setDiscoveryHandlerId("handler");
+ relation.setSelectorId("selector");
+ dto.setDiscoveryRel(relation);
+ when(discoveryMapper.selectAll()).thenReturn(Collections.emptyList());
+ when(discoveryMapper.insert(any())).thenReturn(1);
+ assertEquals(1,
discoveryService.importData(Collections.singletonList(dto)).getSuccessCount());
+ ArgumentCaptor<DiscoveryHandlerDO> handlerCaptor =
ArgumentCaptor.forClass(DiscoveryHandlerDO.class);
+ ArgumentCaptor<DiscoveryRelDO> relationCaptor =
ArgumentCaptor.forClass(DiscoveryRelDO.class);
+
verify(discoveryHandlerMapper).insertSelective(handlerCaptor.capture());
+ verify(discoveryRelMapper).insertSelective(relationCaptor.capture());
+ assertEquals(dto.getId(), handlerCaptor.getValue().getDiscoveryId());
+ assertEquals("handler", handlerCaptor.getValue().getId());
+ assertEquals("{}", handlerCaptor.getValue().getHandler());
+ assertEquals("relation", relationCaptor.getValue().getId());
+ assertEquals("handler",
relationCaptor.getValue().getDiscoveryHandlerId());
+ assertEquals("selector", relationCaptor.getValue().getSelectorId());
+ }
+
+ @Test
+ public void testNamespaceImportMapsHandlerAndSelectorIds() {
+ DiscoveryDTO dto = new DiscoveryDTO();
+ dto.setName("new");
+ dto.setPluginName("divide");
+ dto.setType("local");
+ DiscoveryHandlerDTO handler = new DiscoveryHandlerDTO();
+ handler.setId("old-handler");
+ dto.setDiscoveryHandler(handler);
+ DiscoveryRelDTO relation = new DiscoveryRelDTO();
+ relation.setSelectorId("old-selector");
+ relation.setProxySelectorId("old-proxy");
+ dto.setDiscoveryRel(relation);
+ ConfigsImportContext context = new ConfigsImportContext();
+ context.getSelectorIdMapping().put("old-selector", "new-selector");
+ context.getProxySelectorIdMapping().put("old-proxy", "new-proxy");
+
when(discoveryMapper.selectAllByNamespaceId("ns")).thenReturn(Collections.emptyList());
+ when(discoveryMapper.insert(any())).thenReturn(1);
+ assertEquals(1, discoveryService.importData("ns",
Collections.singletonList(dto), context).getSuccessCount());
+ ArgumentCaptor<DiscoveryHandlerDO> handlerCaptor =
ArgumentCaptor.forClass(DiscoveryHandlerDO.class);
+ ArgumentCaptor<DiscoveryRelDO> relationCaptor =
ArgumentCaptor.forClass(DiscoveryRelDO.class);
+
verify(discoveryHandlerMapper).insertSelective(handlerCaptor.capture());
+ verify(discoveryRelMapper).insertSelective(relationCaptor.capture());
+ String newHandlerId =
context.getDiscoveryHandlerIdMapping().get("old-handler");
+ assertNotNull(newHandlerId);
+ assertEquals(newHandlerId, handlerCaptor.getValue().getId());
+ assertEquals(dto.getId(), handlerCaptor.getValue().getDiscoveryId());
+ assertEquals(newHandlerId,
relationCaptor.getValue().getDiscoveryHandlerId());
+ assertEquals("new-selector",
relationCaptor.getValue().getSelectorId());
+ assertEquals("new-proxy",
relationCaptor.getValue().getProxySelectorId());
+ assertEquals("ns", dto.getNamespaceId());
+ }
+
+ @Test
+ public void testUpdateReturnsNullWhenNoRecordIsUpdated() {
+ DiscoveryDTO dto = new DiscoveryDTO();
+ dto.setId("missing");
+ when(discoveryMapper.updateSelective(any())).thenReturn(0);
+ assertNull(discoveryService.createOrUpdate(dto));
+ verifyNoInteractions(discoveryProcessor);
+ }
+
+ @Test
+ public void testFindDiscoveryHandlerWhenSelectorIsNotBound() {
+
assertNull(discoveryService.findDiscoveryHandlerBySelectorId("unbound"));
+ verify(discoveryHandlerMapper).selectBySelectorId("unbound");
+ }
+
+ @Test
+ public void testDiscoveryLookupMapsRecord() {
+ DiscoveryDO discovery =
DiscoveryDO.builder().id("d").discoveryType("local")
+
.pluginName("divide").namespaceId("ns").discoveryLevel("0").serverList("localhost:8848").props("{}").build();
+
when(discoveryMapper.selectByPluginNameAndLevelAndNamespaceId("divide", "0",
"ns")).thenReturn(discovery);
+ DiscoveryVO result = discoveryService.discovery("divide", "0", "ns");
+ assertEquals("d", result.getId());
+ assertEquals("local", result.getDiscoveryType());
+ assertEquals("divide", result.getPluginName());
+ assertEquals("ns", result.getNamespaceId());
+ assertEquals("0", result.getLevel());
+ assertEquals("localhost:8848", result.getServerList());
+ assertEquals("{}", result.getProps());
+ }
+
+ @Test
+ public void testNamespaceImportRejectsDuplicateName() {
+ DiscoveryDTO dto = new DiscoveryDTO();
+ dto.setName("duplicate");
+ dto.setPluginName("divide");
+
when(discoveryMapper.selectAllByNamespaceId("ns")).thenReturn(Collections.singletonList(
+
DiscoveryDO.builder().pluginName("divide").discoveryName("duplicate").build()));
+ ConfigImportResult result = discoveryService.importData("ns",
Collections.singletonList(dto), new ConfigsImportContext());
+ assertEquals(0, result.getSuccessCount());
+ assertEquals("import fail discovery: duplicate",
result.getFailMessage());
+ verify(discoveryMapper, never()).insert(any());
+ verifyNoInteractions(discoveryHandlerMapper, discoveryRelMapper,
discoveryProcessor);
+ }
}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceUserServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceUserServiceImplTest.java
new file mode 100644
index 0000000000..b9b0c2ccdb
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/NamespaceUserServiceImplTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.exception.ShenyuAdminException;
+import org.apache.shenyu.admin.mapper.NamespaceUserRelMapper;
+import org.apache.shenyu.admin.model.entity.NamespaceDO;
+import org.apache.shenyu.admin.model.entity.NamespaceUserRelDO;
+import org.apache.shenyu.admin.model.event.namespace.NamespaceCreatedEvent;
+import org.apache.shenyu.admin.service.impl.NamespaceUserServiceImpl;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+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.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link NamespaceUserServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class NamespaceUserServiceImplTest {
+ @Mock
+ private NamespaceUserRelMapper mapper;
+
+ @InjectMocks
+ private NamespaceUserServiceImpl service;
+
+ @Test
+ public void testCreateAndList() {
+ when(mapper.selectByNamespaceIdAndUserId("n", "u")).thenReturn(null);
+ assertEquals("n", service.create("n", "u").getNamespaceId());
+
when(mapper.selectListByUserId("u")).thenReturn(Arrays.asList(NamespaceUserRelDO.builder().namespaceId("n1").build(),
+ NamespaceUserRelDO.builder().namespaceId("n2").build()));
+ assertEquals(Arrays.asList("n1", "n2"),
service.listNamespaceIdByUserId("u"));
+ when(mapper.selectListByUserId("empty")).thenReturn(null);
+ assertEquals(Collections.emptyList(),
service.listNamespaceIdByUserId("empty"));
+ verify(mapper).insertSelective(any(NamespaceUserRelDO.class));
+ }
+
+ @Test
+ public void testDuplicateAndNamespaceEvent() {
+ when(mapper.selectByNamespaceIdAndUserId("n",
"u")).thenReturn(NamespaceUserRelDO.builder().build());
+ assertThrows(ShenyuAdminException.class, () -> service.create("n",
"u"));
+ NamespaceDO namespace =
NamespaceDO.builder().namespaceId("n2").build();
+ when(mapper.selectByNamespaceIdAndUserId("n2", "u2")).thenReturn(null);
+ service.onNamespaceCreated(new NamespaceCreatedEvent(namespace, "u2"));
+ verify(mapper).selectByNamespaceIdAndUserId("n2", "u2");
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/OperationRecordLogServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/OperationRecordLogServiceImplTest.java
new file mode 100644
index 0000000000..d19c506016
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/OperationRecordLogServiceImplTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.config.properties.DashboardProperties;
+import org.apache.shenyu.admin.exception.ValidFailException;
+import org.apache.shenyu.admin.mapper.OperationRecordLogMapper;
+import org.apache.shenyu.admin.model.custom.UserInfo;
+import org.apache.shenyu.admin.model.query.RecordLogQueryCondition;
+import org.apache.shenyu.admin.service.impl.OperationRecordLogServiceImpl;
+import org.apache.shenyu.admin.utils.SessionUtil;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+
+import java.util.Collections;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link OperationRecordLogServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class OperationRecordLogServiceImplTest {
+ @Mock
+ private OperationRecordLogMapper mapper;
+
+ @Mock
+ private DashboardProperties properties;
+
+ @InjectMocks
+ private OperationRecordLogServiceImpl service;
+
+ @AfterEach
+ public void cleanSession() {
+ SessionUtil.clean();
+ }
+
+ @Test
+ public void testConditionSearchAndList() {
+
SessionUtil.setLocalVisitor(UserInfo.builder().userName("operator").build());
+ RecordLogQueryCondition condition = new RecordLogQueryCondition();
+ condition.setStartTime(new Date(1_000));
+ condition.setEndTime(new Date(2_000));
+ service.doConditionPreProcessing(condition);
+ assertEquals("operator", condition.getUsername());
+
when(mapper.selectByCondition(condition)).thenReturn(Collections.emptyList());
+ assertTrue(service.searchByCondition(condition).isEmpty());
+ when(properties.getRecordLogLimit()).thenReturn(10);
+ when(mapper.selectLimit("operator",
10)).thenReturn(Collections.emptyList());
+ assertTrue(service.list().isEmpty());
+ verify(mapper).selectByCondition(condition);
+ verify(mapper).selectLimit("operator", 10);
+ }
+
+ @Test
+ public void testAdminConditionPreservesUsernameAndListsAllUsers() {
+
SessionUtil.setLocalVisitor(UserInfo.builder().userName(org.apache.shenyu.common.constant.AdminConstants.ADMIN_NAME).build());
+ RecordLogQueryCondition adminCondition = new RecordLogQueryCondition();
+ adminCondition.setStartTime(new Date(1_000));
+ adminCondition.setEndTime(new Date(2_000));
+ adminCondition.setUsername("requested-user");
+ service.doConditionPreProcessing(adminCondition);
+ assertEquals("requested-user", adminCondition.getUsername());
+ when(properties.getRecordLogLimit()).thenReturn(10);
+ service.list();
+ verify(mapper).selectLimit(null, 10);
+ }
+
+ @Test
+ public void testInvalidConditionAndCleanHistory() {
+ RecordLogQueryCondition condition = new RecordLogQueryCondition();
+ condition.setStartTime(new Date(2_000));
+ condition.setEndTime(new Date(1_000));
+ assertThrows(ValidFailException.class, () ->
service.doConditionPreProcessing(condition));
+ when(properties.getOnlyCleanDays()).thenReturn(1);
+ assertThrows(ValidFailException.class, () -> service.cleanHistory(new
Date()));
+ when(mapper.deleteByBefore(any(Date.class))).thenReturn(1);
+ assertTrue(service.cleanHistory(new Date(System.currentTimeMillis() -
3L * 24 * 60 * 60 * 1000)));
+ }
+
+ @Test
+ public void testCleanHistoryReturnsFalseWhenNothingIsDeleted() {
+ when(properties.getOnlyCleanDays()).thenReturn(1);
+ when(mapper.deleteByBefore(any(Date.class))).thenReturn(0);
+ assertFalse(service.cleanHistory(new Date(System.currentTimeMillis() -
3L * 24 * 60 * 60 * 1000)));
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RegistryServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RegistryServiceImplTest.java
new file mode 100644
index 0000000000..be858eec6b
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/RegistryServiceImplTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.exception.ShenyuAdminException;
+import org.apache.shenyu.admin.mapper.RegistryMapper;
+import org.apache.shenyu.admin.model.dto.RegistryDTO;
+import org.apache.shenyu.admin.model.entity.RegistryDO;
+import org.apache.shenyu.admin.model.vo.RegistryVO;
+import org.apache.shenyu.admin.service.impl.RegistryServiceImpl;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+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.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link RegistryServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class RegistryServiceImplTest {
+
+ @Mock
+ private RegistryMapper registryMapper;
+
+ @InjectMocks
+ private RegistryServiceImpl registryService;
+
+ @Test
+ public void testCreateAndFindMethods() {
+ RegistryDTO dto = new RegistryDTO();
+ dto.setRegistryId("r1");
+ dto.setProtocol("http");
+ dto.setAddress("localhost:8848");
+ when(registryMapper.selectByRegistryId("r1")).thenReturn(null);
+
when(registryMapper.selectAll()).thenReturn(Collections.singletonList(RegistryDO.builder().id("id").registryId("r1").build()));
+ assertNotNull(registryService.createOrUpdate(dto));
+ verify(registryMapper).insert(any(RegistryDO.class));
+ registryService.findById("id");
+ registryService.findByRegistryId("r1");
+ registryService.listAll();
+ verify(registryMapper).selectById("id");
+ verify(registryMapper,
org.mockito.Mockito.times(2)).selectByRegistryId("r1");
+ }
+
+ @Test
+ public void testCreateRejectsDuplicateAndUpdateBranches() {
+ RegistryDTO dto = new RegistryDTO();
+ dto.setRegistryId("r1");
+
when(registryMapper.selectByRegistryId("r1")).thenReturn(RegistryDO.builder().id("other").build());
+ assertThrows(ShenyuAdminException.class, () ->
registryService.createOrUpdate(dto));
+
+ RegistryDTO missingId = new RegistryDTO();
+ missingId.setRegistryId("r1");
+ missingId.setId(null);
+ when(registryMapper.selectByRegistryId("r1")).thenReturn(null);
+ when(registryMapper.insert(any(RegistryDO.class))).thenReturn(1);
+ assertNotNull(registryService.createOrUpdate(missingId));
+
+ RegistryDTO update = new RegistryDTO();
+ update.setId("id");
+ update.setRegistryId("r1");
+
when(registryMapper.selectByRegistryId("r1")).thenReturn(RegistryDO.builder().id("id").build());
+
when(registryMapper.updateSelective(any(RegistryDO.class))).thenReturn(0);
+ assertNull(registryService.createOrUpdate(update));
+
+
when(registryMapper.selectByRegistryId("r1")).thenReturn(RegistryDO.builder().id("other").build());
+ assertThrows(ShenyuAdminException.class, () ->
registryService.createOrUpdate(update));
+ verify(registryMapper, never()).deleteByIds(any());
+ }
+
+ @Test
+ public void testUpdateSuccessReturnsRegistry() {
+ RegistryDTO update = new RegistryDTO();
+ update.setId("id");
+ update.setRegistryId("r1");
+ update.setProtocol("http");
+ update.setAddress("localhost:8848");
+ update.setNamespace("registry-namespace");
+ update.setGroup("group");
+ update.setUsername("user");
+ update.setPassword("password");
+
when(registryMapper.selectByRegistryId("r1")).thenReturn(RegistryDO.builder().id("id").build());
+
when(registryMapper.updateSelective(any(RegistryDO.class))).thenReturn(1);
+ RegistryVO result = registryService.createOrUpdate(update);
+ assertNotNull(result);
+ assertEquals("id", result.getId());
+ assertEquals("r1", result.getRegistryId());
+ assertEquals("http", result.getProtocol());
+ assertEquals("localhost:8848", result.getAddress());
+ assertEquals("registry-namespace", result.getNamespace());
+ assertEquals("group", result.getGroup());
+ assertEquals("user", result.getUsername());
+ assertEquals("password", result.getPassword());
+ }
+
+ @Test
+ public void testListAndDelete() {
+ when(registryMapper.countByQuery(any())).thenReturn(0);
+ org.apache.shenyu.admin.model.query.RegistryQuery query = new
org.apache.shenyu.admin.model.query.RegistryQuery();
+ query.setPageParameter(new
org.apache.shenyu.admin.model.page.PageParameter());
+ registryService.listByPage(query);
+
assertEquals(org.apache.shenyu.admin.utils.ShenyuResultMessage.DELETE_SUCCESS,
+ registryService.delete(Collections.singletonList("id")));
+ verify(registryMapper).deleteByIds(any());
+ }
+
+ @Test
+ public void testListByPageWithResultsAndFindMappings() {
+ org.apache.shenyu.admin.model.query.RegistryQuery query = new
org.apache.shenyu.admin.model.query.RegistryQuery();
+ query.setPageParameter(new
org.apache.shenyu.admin.model.page.PageParameter());
+ RegistryDO registry =
RegistryDO.builder().id("id").registryId("r1").build();
+ when(registryMapper.countByQuery(query)).thenReturn(1);
+
when(registryMapper.selectByQuery(query)).thenReturn(Collections.singletonList(registry));
+ assertEquals(1,
registryService.listByPage(query).getDataList().size());
+ when(registryMapper.selectById("id")).thenReturn(registry);
+ when(registryMapper.selectByRegistryId("r1")).thenReturn(registry);
+ assertEquals("id", registryService.findById("id").getId());
+ assertEquals("id", registryService.findByRegistryId("r1").getId());
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScalePolicyServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScalePolicyServiceImplTest.java
new file mode 100644
index 0000000000..7b52116d96
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScalePolicyServiceImplTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.ScalePolicyMapper;
+import org.apache.shenyu.admin.model.dto.ScalePolicyDTO;
+import org.apache.shenyu.admin.model.entity.ScalePolicyDO;
+import org.apache.shenyu.admin.scale.scaler.cache.ScalePolicyCache;
+import org.apache.shenyu.admin.scale.scaler.ScaleService;
+import org.apache.shenyu.admin.service.impl.ScalePolicyServiceImpl;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+import
org.springframework.transaction.support.TransactionSynchronizationManager;
+import org.springframework.transaction.support.TransactionSynchronizationUtils;
+
+import java.sql.Timestamp;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link ScalePolicyServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class ScalePolicyServiceImplTest {
+ @Mock
+ private ScalePolicyMapper mapper;
+
+ @Mock
+ private ScalePolicyCache cache;
+
+ @Mock
+ private ScaleService scaleService;
+
+ @InjectMocks
+ private ScalePolicyServiceImpl service;
+
+ @AfterEach
+ public void tearDown() {
+ if (TransactionSynchronizationManager.isSynchronizationActive()) {
+ TransactionSynchronizationManager.clearSynchronization();
+ }
+ }
+
+ @Test
+ public void testSelectAndFind() {
+ Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+ ScalePolicyDO policy =
ScalePolicyDO.builder().id("p").dateCreated(timestamp).dateUpdated(timestamp).build();
+ when(mapper.selectAll()).thenReturn(Collections.singletonList(policy));
+ assertEquals("p", service.selectAll().get(0).getId());
+ when(mapper.selectByPrimaryKey("p")).thenReturn(policy);
+ assertEquals("p", service.findById("p").getId());
+ verify(mapper).selectByPrimaryKey("p");
+ }
+
+ @Test
+ public void testUpdateImmediatelyAndAfterCommit() {
+
when(mapper.updateByPrimaryKeySelective(any(ScalePolicyDO.class))).thenReturn(1);
+ service.update(ScalePolicyDTO.builder().id("p").status(1).build());
+ verify(cache, times(1)).updatePolicy(any(ScalePolicyDO.class));
+ verify(scaleService).executeScaling();
+
+ TransactionSynchronizationManager.initSynchronization();
+ service.update(ScalePolicyDTO.builder().id("p2").status(0).build());
+ verify(cache, times(1)).updatePolicy(any(ScalePolicyDO.class));
+
TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationManager.getSynchronizations());
+ verify(cache, times(2)).updatePolicy(any(ScalePolicyDO.class));
+ verify(scaleService, times(2)).executeScaling();
+ }
+
+ @Test
+ public void testSelectAllReturnsEmptyList() {
+ when(mapper.selectAll()).thenReturn(Collections.emptyList());
+ assertEquals(Collections.emptyList(), service.selectAll());
+ }
+
+ @Test
+ public void testUpdateSkipsPolicyApplicationWhenMapperReturnsZero() {
+
when(mapper.updateByPrimaryKeySelective(any(ScalePolicyDO.class))).thenReturn(0);
+ assertEquals(0,
service.update(ScalePolicyDTO.builder().id("p").status(1).build()));
+ verify(cache,
org.mockito.Mockito.never()).updatePolicy(any(ScalePolicyDO.class));
+ verify(scaleService, org.mockito.Mockito.never()).executeScaling();
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScaleRuleServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScaleRuleServiceTest.java
index 56c499d63e..a49815f7db 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScaleRuleServiceTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ScaleRuleServiceTest.java
@@ -20,20 +20,26 @@ package org.apache.shenyu.admin.service;
import org.apache.shenyu.admin.mapper.ScaleRuleMapper;
import org.apache.shenyu.admin.model.dto.ScaleRuleDTO;
import org.apache.shenyu.admin.model.entity.ScaleRuleDO;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.ScaleRuleQuery;
import org.apache.shenyu.admin.scale.monitor.subject.cache.ScaleRuleCache;
import org.apache.shenyu.admin.service.impl.ScaleRuleServiceImpl;
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.junit.jupiter.api.Test;
import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
import
org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
+import java.sql.Timestamp;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
/**
@@ -82,4 +88,107 @@ public final class ScaleRuleServiceTest {
verify(scaleRuleCache).addOrUpdateRuleToCache(any(ScaleRuleDO.class));
}
+
+ @Test
+ public void testReadUpdateAndDelete() {
+ Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+
when(scaleRuleMapper.selectAll()).thenReturn(java.util.Collections.singletonList(ScaleRuleDO.builder().id("r")
+ .dateCreated(timestamp).dateUpdated(timestamp).build()));
+ scaleRuleService.selectAll();
+ scaleRuleService.findById("r");
+ ScaleRuleQuery query = new ScaleRuleQuery();
+ query.setPageParameter(new PageParameter());
+
when(scaleRuleMapper.selectByQuery(query)).thenReturn(java.util.Collections.emptyList());
+ scaleRuleService.listByPage(query);
+ ScaleRuleDTO dto = new ScaleRuleDTO();
+ dto.setId("r");
+ dto.setMetricName("new");
+
when(scaleRuleMapper.selectByPrimaryKey("r")).thenReturn(ScaleRuleDO.builder().id("r").metricName("old").build(),
+ ScaleRuleDO.builder().id("r").metricName("new").build());
+
when(scaleRuleMapper.updateByPrimaryKeySelective(any(ScaleRuleDO.class))).thenReturn(1);
+ TransactionSynchronizationManager.initSynchronization();
+ scaleRuleService.update(dto);
+
TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationManager.getSynchronizations());
+ verify(scaleRuleCache).removeRulesFromCache(any());
+ verify(scaleRuleCache).addOrUpdateRuleToCache(any(ScaleRuleDO.class));
+ TransactionSynchronizationManager.clearSynchronization();
+ when(scaleRuleMapper.delete(any())).thenReturn(1);
+ scaleRuleService.delete(java.util.Collections.singletonList("r"));
+ verify(scaleRuleCache).removeRulesByIdsFromCache(any());
+ }
+
+ @Test
+ public void
testCreateOrUpdateDispatchesAndCreateSkipsCacheWhenInsertFails() {
+
when(scaleRuleMapper.insertSelective(any(ScaleRuleDO.class))).thenReturn(0);
+ assertEquals(0, scaleRuleService.createOrUpdate(new ScaleRuleDTO()));
+ verifyNoInteractions(scaleRuleCache);
+ ScaleRuleDTO update = new ScaleRuleDTO();
+ update.setId("r");
+ ScaleRuleDO persisted =
ScaleRuleDO.builder().id("r").metricName("metric").build();
+ when(scaleRuleMapper.selectByPrimaryKey("r")).thenReturn(null,
persisted);
+
when(scaleRuleMapper.updateByPrimaryKeySelective(any(ScaleRuleDO.class))).thenReturn(1);
+ assertEquals(1, scaleRuleService.createOrUpdate(update));
+ verify(scaleRuleMapper).insertSelective(any(ScaleRuleDO.class));
+
verify(scaleRuleMapper).updateByPrimaryKeySelective(any(ScaleRuleDO.class));
+ verify(scaleRuleCache).addOrUpdateRuleToCache(persisted);
+ }
+
+ @Test
+ public void testReadMethodsWithResults() {
+ Timestamp timestamp = new Timestamp(System.currentTimeMillis());
+ ScaleRuleDO rule = ScaleRuleDO.builder().id("r").metricName("metric")
+ .dateCreated(timestamp).dateUpdated(timestamp).build();
+ ScaleRuleQuery query = new ScaleRuleQuery();
+ query.setPageParameter(new PageParameter());
+
when(scaleRuleMapper.selectByQuery(query)).thenReturn(java.util.Collections.singletonList(rule));
+ assertEquals(1,
scaleRuleService.listByPage(query).getDataList().size());
+ when(scaleRuleMapper.selectByPrimaryKey("r")).thenReturn(rule);
+ assertEquals("r", scaleRuleService.findById("r").getId());
+ }
+
+ @Test
+ public void testUpdateAndDeleteSkipCacheWhenMapperReturnsZero() {
+ ScaleRuleDTO dto = new ScaleRuleDTO();
+ dto.setId("r");
+ when(scaleRuleMapper.selectByPrimaryKey("r")).thenReturn(null);
+
when(scaleRuleMapper.updateByPrimaryKeySelective(any(ScaleRuleDO.class))).thenReturn(0);
+ assertEquals(0, scaleRuleService.update(dto));
+ when(scaleRuleMapper.delete(any())).thenReturn(0);
+ assertEquals(0,
scaleRuleService.delete(java.util.Collections.singletonList("r")));
+ verifyNoInteractions(scaleRuleCache);
+ }
+
+ @Test
+ public void testUpdatePreservesCacheWhenMetricNameIsUnchanged() {
+ ScaleRuleDTO dto = new ScaleRuleDTO();
+ dto.setId("r");
+ dto.setMetricName("metric");
+ ScaleRuleDO persisted =
ScaleRuleDO.builder().id("r").metricName("metric").build();
+ when(scaleRuleMapper.selectByPrimaryKey("r")).thenReturn(persisted);
+
when(scaleRuleMapper.updateByPrimaryKeySelective(any(ScaleRuleDO.class))).thenReturn(1);
+ assertEquals(1, scaleRuleService.update(dto));
+ verify(scaleRuleCache, never()).removeRulesFromCache(any());
+ verify(scaleRuleCache).addOrUpdateRuleToCache(persisted);
+ }
+
+ @Test
+ public void testUpdateWithoutPreviousRuleAndDeleteAfterCommit() {
+ ScaleRuleDTO dto = new ScaleRuleDTO();
+ dto.setId("r");
+ when(scaleRuleMapper.selectByPrimaryKey("r")).thenReturn(null,
+ ScaleRuleDO.builder().id("r").metricName("metric").build());
+
when(scaleRuleMapper.updateByPrimaryKeySelective(any(ScaleRuleDO.class))).thenReturn(1);
+ TransactionSynchronizationManager.initSynchronization();
+ scaleRuleService.update(dto);
+
TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationManager.getSynchronizations());
+ verify(scaleRuleCache, never()).removeRulesFromCache(any());
+ verify(scaleRuleCache).addOrUpdateRuleToCache(any(ScaleRuleDO.class));
+ TransactionSynchronizationManager.clearSynchronization();
+ when(scaleRuleMapper.delete(any())).thenReturn(1);
+ TransactionSynchronizationManager.initSynchronization();
+ scaleRuleService.delete(java.util.Collections.singletonList("r"));
+ verify(scaleRuleCache, never()).removeRulesByIdsFromCache(any());
+
TransactionSynchronizationUtils.invokeAfterCommit(TransactionSynchronizationManager.getSynchronizations());
+ verify(scaleRuleCache).removeRulesByIdsFromCache(any());
+ }
}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/manager/RegisterApiDocServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/manager/RegisterApiDocServiceImplTest.java
new file mode 100644
index 0000000000..7db5de53eb
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/manager/RegisterApiDocServiceImplTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.manager;
+
+import org.apache.shenyu.admin.model.dto.ApiDTO;
+import org.apache.shenyu.admin.model.dto.TagDTO;
+import org.apache.shenyu.admin.model.vo.TagVO;
+import org.apache.shenyu.admin.service.ApiService;
+import org.apache.shenyu.admin.service.manager.impl.RegisterApiDocServiceImpl;
+import org.apache.shenyu.admin.service.TagService;
+import org.apache.shenyu.register.common.dto.ApiDocRegisterDTO;
+import org.apache.shenyu.register.common.enums.EventType;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link RegisterApiDocServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class RegisterApiDocServiceImplTest {
+ @Mock
+ private ApiService apiService;
+
+ @Mock
+ private TagService tagService;
+
+ @InjectMocks
+ private RegisterApiDocServiceImpl service;
+
+ @Test
+ public void testRegisterUsesExistingAndCreatesMissingTags() {
+ ReflectionTestUtils.setField(service, "apiService", apiService);
+ ReflectionTestUtils.setField(service, "tagService", tagService);
+ ApiDocRegisterDTO dto = new ApiDocRegisterDTO();
+ dto.setEventType(EventType.REGISTER);
+ dto.setApiPath("/a");
+ dto.setHttpMethod(1);
+ dto.setRpcType("http");
+ dto.setTags(Collections.singletonList("tag"));
+
when(tagService.findByQuery("tag")).thenReturn(Collections.emptyList());
+ service.registerApiDocument(dto);
+ verify(apiService).deleteByApiPathHttpMethodRpcType("/a", 1, "http");
+ verify(tagService).createRootTag(any(), any());
+ verify(apiService).createOrUpdate(any());
+ }
+
+ @Test
+ public void testRegisterWithExistingTagAndOffline() {
+ ReflectionTestUtils.setField(service, "apiService", apiService);
+ ReflectionTestUtils.setField(service, "tagService", tagService);
+ ApiDocRegisterDTO dto = new ApiDocRegisterDTO();
+ dto.setEventType(EventType.REGISTER);
+ dto.setContextPath("/ctx");
+ dto.setTags(Collections.singletonList("tag"));
+ TagVO tag = new TagVO();
+ tag.setId("tag-id");
+
when(tagService.findByQuery("tag")).thenReturn(Collections.singletonList(tag));
+ service.registerApiDocument(dto);
+ verify(apiService).createOrUpdate(any());
+ ApiDocRegisterDTO offline = new ApiDocRegisterDTO();
+ offline.setEventType(EventType.OFFLINE);
+ offline.setContextPath("/ctx");
+ service.registerApiDocument(offline);
+ verify(apiService).offlineByContextPath("/ctx");
+ }
+
+ @Test
+ public void testRegisterUsesContextPathWhenTagsAreAbsent() {
+ ReflectionTestUtils.setField(service, "apiService", apiService);
+ ReflectionTestUtils.setField(service, "tagService", tagService);
+ ApiDocRegisterDTO dto = new ApiDocRegisterDTO();
+ dto.setEventType(EventType.REGISTER);
+ dto.setContextPath("/ctx");
+
when(tagService.findByQuery("/ctx")).thenReturn(Collections.emptyList());
+ service.registerApiDocument(dto);
+ verify(tagService).createRootTag(any(), any());
+ verify(apiService).createOrUpdate(any());
+ }
+
+ @Test
+ public void testRegisterIgnoresOtherEventTypes() {
+ ReflectionTestUtils.setField(service, "apiService", apiService);
+ ReflectionTestUtils.setField(service, "tagService", tagService);
+ ApiDocRegisterDTO dto = new ApiDocRegisterDTO();
+ dto.setEventType(EventType.UPDATED);
+ service.registerApiDocument(dto);
+ org.mockito.Mockito.verifyNoInteractions(apiService, tagService);
+ }
+
+ @Test
+ public void testRegisterCombinesExistingAndNewTagsAndCopiesApiFields() {
+ ApiDocRegisterDTO dto = new ApiDocRegisterDTO();
+ dto.setEventType(EventType.REGISTER);
+ dto.setApiPath("/ctx/api");
+ dto.setContextPath("/ctx");
+ dto.setHttpMethod(1);
+ dto.setRpcType("http");
+ dto.setDocument("document");
+ dto.setApiSource(1);
+ dto.setApiOwner("owner");
+ dto.setApiDesc("description");
+ dto.setExt("{}");
+ dto.setVersion("v1");
+ dto.setConsume("application/json");
+ dto.setProduce("application/json");
+ dto.setState(1);
+ dto.setTags(List.of("existing", "new"));
+ TagVO tag = new TagVO();
+ tag.setId("existing-id");
+
when(tagService.findByQuery("existing")).thenReturn(Collections.singletonList(tag));
+
when(tagService.findByQuery("new")).thenReturn(Collections.emptyList());
+ service.registerApiDocument(dto);
+ ArgumentCaptor<TagDTO> tagCaptor =
ArgumentCaptor.forClass(TagDTO.class);
+ verify(tagService).createRootTag(tagCaptor.capture(),
org.mockito.ArgumentMatchers.isNull());
+ ArgumentCaptor<ApiDTO> apiCaptor =
ArgumentCaptor.forClass(ApiDTO.class);
+ verify(apiService).createOrUpdate(apiCaptor.capture());
+ ApiDTO api = apiCaptor.getValue();
+ assertNotNull(tagCaptor.getValue().getId());
+ assertEquals(List.of("existing-id", tagCaptor.getValue().getId()),
api.getTagIds());
+ assertEquals("new", tagCaptor.getValue().getTagName());
+ assertEquals("/ctx/api", api.getApiPath());
+ assertEquals("/ctx", api.getContextPath());
+ assertEquals(1, api.getHttpMethod());
+ assertEquals("http", api.getRpcType());
+ assertEquals("document", api.getDocument());
+ assertEquals(1, api.getApiSource());
+ assertEquals("owner", api.getApiOwner());
+ assertEquals("description", api.getApiDesc());
+ assertEquals("{}", api.getExt());
+ assertEquals("v1", api.getVersion());
+ assertEquals("application/json", api.getConsume());
+ assertEquals("application/json", api.getProduce());
+ assertEquals(1, api.getState());
+ }
+}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/register/ShenyuClientRegisterMcpServiceImplTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/register/ShenyuClientRegisterMcpServiceImplTest.java
new file mode 100644
index 0000000000..fc91e558c2
--- /dev/null
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/register/ShenyuClientRegisterMcpServiceImplTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.register;
+
+import org.apache.shenyu.admin.mapper.NamespacePluginRelMapper;
+import org.apache.shenyu.admin.mapper.PluginMapper;
+import org.apache.shenyu.admin.model.dto.RuleConditionDTO;
+import org.apache.shenyu.admin.model.dto.RuleDTO;
+import org.apache.shenyu.admin.model.entity.MetaDataDO;
+import org.apache.shenyu.admin.model.entity.PluginDO;
+import org.apache.shenyu.admin.service.MetaDataService;
+import org.apache.shenyu.admin.service.RuleService;
+import org.apache.shenyu.admin.service.SelectorService;
+import org.apache.shenyu.common.enums.MatchModeEnum;
+import org.apache.shenyu.common.enums.OperatorEnum;
+import org.apache.shenyu.common.enums.ParamTypeEnum;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.common.utils.PluginNameAdapter;
+import org.apache.shenyu.register.common.dto.McpToolsRegisterDTO;
+import org.apache.shenyu.register.common.dto.MetaDataRegisterDTO;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link ShenyuClientRegisterMcpServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class ShenyuClientRegisterMcpServiceImplTest {
+ @InjectMocks
+ private ShenyuClientRegisterMcpServiceImpl service;
+
+ @Mock
+ private MetaDataService metaDataService;
+
+ @Mock
+ private SelectorService selectorService;
+
+ @Mock
+ private RuleService ruleService;
+
+ @Mock
+ private PluginMapper pluginMapper;
+
+ @Mock
+ private NamespacePluginRelMapper namespacePluginRelMapper;
+
+ @Test
+ public void testRpcTypeAndRegisterTools() {
+ ReflectionTestUtils.setField(service, "metaDataService",
metaDataService);
+ ReflectionTestUtils.setField(service, "selectorService",
selectorService);
+ ReflectionTestUtils.setField(service, "ruleService", ruleService);
+ ShenyuClientRegisterMcpServiceImpl spy =
org.mockito.Mockito.spy(service);
+ doNothing().when(spy).checkNamespacePluginRel(anyString(),
anyString());
+ MetaDataRegisterDTO metadata =
MetaDataRegisterDTO.builder().path("/tool").ruleName("tool")
+ .namespaceId("ns").contextPath("").build();
+ McpToolsRegisterDTO dto = new McpToolsRegisterDTO();
+ dto.setMetaDataRegisterDTO(metadata);
+ dto.setMcpConfig("{}");
+ dto.setNamespaceId("ns");
+ when(selectorService.registerDefault(any(), anyString(),
anyString())).thenReturn("selector");
+ when(metaDataService.findByPathAndNamespaceId("/tool",
"ns")).thenReturn(null);
+ spy.registerMcpTools(dto);
+ assertEquals(RpcTypeEnum.MCP.getName(), spy.rpcType());
+ verify(spy).checkNamespacePluginRel("ns",
PluginNameAdapter.rpcTypeAdapter(RpcTypeEnum.MCP.getName()));
+ verify(selectorService).registerDefault(any(), anyString(),
anyString());
+ ArgumentCaptor<RuleDTO> captor =
ArgumentCaptor.forClass(RuleDTO.class);
+ verify(ruleService).registerDefault(captor.capture());
+ RuleDTO rule = captor.getValue();
+ assertEquals("selector", rule.getSelectorId());
+ assertEquals("tool", rule.getName());
+ assertEquals("{}", rule.getHandle());
+ assertEquals("ns", rule.getNamespaceId());
+ assertEquals(MatchModeEnum.AND.getCode(), rule.getMatchMode());
+ assertEquals(Boolean.TRUE, rule.getEnabled());
+ assertEquals(Boolean.TRUE, rule.getLoged());
+ assertEquals(Boolean.FALSE, rule.getMatchRestful());
+ assertEquals(1, rule.getSort());
+ assertEquals(1, rule.getRuleConditions().size());
+ RuleConditionDTO condition = rule.getRuleConditions().get(0);
+ assertEquals(ParamTypeEnum.URI.getName(), condition.getParamType());
+ assertEquals("/", condition.getParamName());
+ assertEquals("/**", condition.getParamValue());
+ assertEquals(OperatorEnum.MATCH.getAlias(), condition.getOperator());
+ verify(metaDataService).saveOrUpdateMetaData(null, metadata);
+ }
+
+ @Test
+ public void testMcpExtensionHandlersReturnEmptyImplementations() {
+
assertNull(service.selectorHandler(MetaDataRegisterDTO.builder().build()));
+ assertNull(service.ruleHandler());
+ assertNull(service.buildHandle(java.util.Collections.emptyList(), new
org.apache.shenyu.admin.model.entity.SelectorDO()));
+ }
+
+ @Test
+ public void testRegisterToolsUsesDefaultNamespaceAndContextPath() {
+ ReflectionTestUtils.setField(service, "metaDataService",
metaDataService);
+ ReflectionTestUtils.setField(service, "selectorService",
selectorService);
+ ReflectionTestUtils.setField(service, "ruleService", ruleService);
+ ShenyuClientRegisterMcpServiceImpl spy =
org.mockito.Mockito.spy(service);
+ doNothing().when(spy).checkNamespacePluginRel(anyString(),
anyString());
+ doNothing().when(spy).registerContextPath(any());
+ MetaDataRegisterDTO metadata =
MetaDataRegisterDTO.builder().path("/tool").ruleName("tool")
+
.namespaceId(org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID).contextPath("/ctx").build();
+ McpToolsRegisterDTO dto = new McpToolsRegisterDTO();
+ dto.setMetaDataRegisterDTO(metadata);
+ dto.setMcpConfig("{}");
+ when(selectorService.registerDefault(any(), anyString(),
anyString())).thenReturn("selector");
+ MetaDataDO existing = MetaDataDO.builder().build();
+ when(metaDataService.findByPathAndNamespaceId("/tool",
org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID))
+ .thenReturn(existing);
+ spy.registerMcpTools(dto);
+
assertEquals(org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID,
dto.getNamespaceId());
+
verify(spy).checkNamespacePluginRel(org.apache.shenyu.common.constant.Constants.SYS_DEFAULT_NAMESPACE_ID,
+
org.apache.shenyu.common.utils.PluginNameAdapter.rpcTypeAdapter(RpcTypeEnum.MCP.getName()));
+ verify(spy).registerContextPath(metadata);
+ verify(metaDataService).saveOrUpdateMetaData(existing, metadata);
+ }
+
+ @Test
+ public void testRegisterToolsStopsWhenPluginDoesNotExist() {
+ ReflectionTestUtils.setField(service, "pluginMapper", pluginMapper);
+ ReflectionTestUtils.setField(service, "namespacePluginRelMapper",
namespacePluginRelMapper);
+ McpToolsRegisterDTO dto = new McpToolsRegisterDTO();
+ dto.setNamespaceId("ns");
+ dto.setMetaDataRegisterDTO(MetaDataRegisterDTO.builder().build());
+ assertThrows(IllegalArgumentException.class, () ->
service.registerMcpTools(dto));
+
verify(pluginMapper).selectByName(PluginNameAdapter.rpcTypeAdapter(RpcTypeEnum.MCP.getName()));
+ verifyNoInteractions(namespacePluginRelMapper, selectorService,
ruleService, metaDataService);
+ }
+
+ @Test
+ public void testRegisterToolsStopsWhenPluginIsNotEnabledInNamespace() {
+ ReflectionTestUtils.setField(service, "pluginMapper", pluginMapper);
+ ReflectionTestUtils.setField(service, "namespacePluginRelMapper",
namespacePluginRelMapper);
+
when(pluginMapper.selectByName(PluginNameAdapter.rpcTypeAdapter(RpcTypeEnum.MCP.getName())))
+ .thenReturn(PluginDO.builder().id("plugin").build());
+ McpToolsRegisterDTO dto = new McpToolsRegisterDTO();
+ dto.setNamespaceId("ns");
+ dto.setMetaDataRegisterDTO(MetaDataRegisterDTO.builder().build());
+ assertThrows(IllegalArgumentException.class, () ->
service.registerMcpTools(dto));
+
verify(namespacePluginRelMapper).selectByPluginIdAndNamespaceId("plugin", "ns");
+ verifyNoInteractions(selectorService, ruleService, metaDataService);
+ }
+}
diff --git
a/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/alert/AlarmServiceImplTest.java
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/alert/AlarmServiceImplTest.java
new file mode 100644
index 0000000000..8ac3158348
--- /dev/null
+++
b/shenyu-plugin/shenyu-plugin-base/src/test/java/org/apache/shenyu/plugin/base/alert/AlarmServiceImplTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.plugin.base.alert;
+
+import org.apache.shenyu.common.dto.AlarmContent;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.junit.jupiter.MockitoExtension;
+import org.mockito.Mock;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.client.RestTemplate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/** Tests for {@link AlarmServiceImpl}. */
+@ExtendWith(MockitoExtension.class)
+public final class AlarmServiceImplTest {
+ @Mock
+ private RestTemplate restTemplate;
+
+ @Test
+ public void testDisabledAndMissingAdmins() {
+ AlarmContent content = new AlarmContent.Builder().build();
+ new AlarmServiceImpl(restTemplate, "localhost:9095",
false).alarm(content);
+ new AlarmServiceImpl(restTemplate, "", true).alarm(content);
+ verifyNoPost();
+ }
+
+ @Test
+ public void testAlarmStopsAfterSuccessfulEndpoint() {
+ AlarmContent content = new AlarmContent.Builder().build();
+
when(restTemplate.postForEntity(contains("localhost:9095/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenReturn(new ResponseEntity<>(HttpStatus.OK));
+ new AlarmServiceImpl(restTemplate, "localhost:9095,localhost:9096",
true).alarm(content);
+
verify(restTemplate).postForEntity(contains("localhost:9095/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ verify(restTemplate,
never()).postForEntity(contains("localhost:9096/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ }
+
+ @Test
+ public void testAlarmContinuesOnFailureAndException() {
+ AlarmContent content = new AlarmContent.Builder().build();
+
when(restTemplate.postForEntity(contains("localhost:9095/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenReturn(new ResponseEntity<>(HttpStatus.BAD_GATEWAY));
+
when(restTemplate.postForEntity(contains("localhost:9096/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenThrow(new RuntimeException("down"));
+ new AlarmServiceImpl(restTemplate, "localhost:9095,localhost:9096",
true).alarm(content);
+ verify(restTemplate,
org.mockito.Mockito.times(2)).postForEntity(org.mockito.ArgumentMatchers.anyString(),
+ org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ }
+
+ @Test
+ public void testAlarmSucceedsOnFallbackEndpoint() {
+ AlarmContent content = new AlarmContent.Builder().build();
+
when(restTemplate.postForEntity(contains("localhost:9095/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenReturn(new ResponseEntity<>(HttpStatus.BAD_GATEWAY));
+
when(restTemplate.postForEntity(contains("localhost:9096/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenReturn(new ResponseEntity<>(HttpStatus.OK));
+ new AlarmServiceImpl(restTemplate,
"localhost:9095,localhost:9096,localhost:9097", true).alarm(content);
+ verify(restTemplate,
org.mockito.Mockito.times(2)).postForEntity(org.mockito.ArgumentMatchers.anyString(),
+ org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ verify(restTemplate,
never()).postForEntity(contains("localhost:9097/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ }
+
+ @Test
+ public void testAlarmRecoversAfterEndpointThrowsException() {
+ AlarmContent content = new AlarmContent.Builder().build();
+
when(restTemplate.postForEntity(contains("localhost:9095/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenThrow(new RuntimeException("unreachable"));
+
when(restTemplate.postForEntity(contains("localhost:9096/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class)))
+ .thenReturn(new ResponseEntity<>(HttpStatus.OK));
+ new AlarmServiceImpl(restTemplate,
"localhost:9095,localhost:9096,localhost:9097", true).alarm(content);
+ ArgumentCaptor<Object> captor = ArgumentCaptor.forClass(Object.class);
+
verify(restTemplate).postForEntity(contains("localhost:9096/alert/report"),
captor.capture(), eq(Void.class));
+ HttpEntity<?> request = (HttpEntity<?>) captor.getValue();
+ assertSame(content, request.getBody());
+ assertEquals(MediaType.APPLICATION_JSON,
request.getHeaders().getContentType());
+ verify(restTemplate,
never()).postForEntity(contains("localhost:9097/alert/report"),
org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ }
+
+ private void verifyNoPost() {
+ verify(restTemplate,
never()).postForEntity(org.mockito.ArgumentMatchers.anyString(),
org.mockito.ArgumentMatchers.any(), eq(Void.class));
+ }
+}