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 e3c05af20c fix: register namespace-prefixed change listener prefixes 
for apollo sync data (#6984)
e3c05af20c is described below

commit e3c05af20cb2ac1a6510bb9868ee169018064622
Author: wy471x <[email protected]>
AuthorDate: Wed Aug 26 15:35:51 2026 +0800

    fix: register namespace-prefixed change listener prefixes for apollo sync 
data (#6984)
    
    Apollo's AbstractConfig matches interested key prefixes with
    changedKey.startsWith(prefix), but real property keys are namespace-prefixed
    (e.g. {namespaceId}.plugin.{pluginName}), so incremental change events were
    silently dropped after the startup load.
    
    - register prefixes built from namespaceId + dataId
    - match change keys in the listener with startsWith(namespaceId + "." + 
dataId)
    - parse the plugin name from the remove key in unCachePluginData
    - add integration test flowing through apollo-client's real listener 
filtering
    
    Co-authored-by: Claude <[email protected]>
    Co-authored-by: aias00 <[email protected]>
---
 .../common/constant/ApolloPathConstants.java       |  13 ++
 .../data/core/AbstractNodeDataSyncService.java     |   9 +-
 .../data/core/AbstractNodeDataSyncServiceTest.java |   9 +-
 .../shenyu/sync/data/apollo/ApolloDataService.java |  22 ++--
 .../apollo/ApolloDataServiceIntegrationTest.java   | 141 +++++++++++++++++++++
 .../sync/data/apollo/ApolloDataServiceTest.java    | 112 ++++++++++++++--
 6 files changed, 276 insertions(+), 30 deletions(-)

diff --git 
a/shenyu-common/src/main/java/org/apache/shenyu/common/constant/ApolloPathConstants.java
 
b/shenyu-common/src/main/java/org/apache/shenyu/common/constant/ApolloPathConstants.java
index e2a7c7d340..79bb4a9238 100644
--- 
a/shenyu-common/src/main/java/org/apache/shenyu/common/constant/ApolloPathConstants.java
+++ 
b/shenyu-common/src/main/java/org/apache/shenyu/common/constant/ApolloPathConstants.java
@@ -95,5 +95,18 @@ public class ApolloPathConstants {
         return new HashSet<>(Arrays.asList(PLUGIN_DATA_ID, SELECTOR_DATA_ID, 
RULE_DATA_ID, AUTH_DATA_ID,
                 META_DATA_ID, PROXY_SELECTOR_DATA_ID, DISCOVERY_DATA_ID));
     }
+
+    /**
+     * get path key set with namespace prefix.
+     * for example, if the namespace id is {@code shenyu}, the plugin path key 
is {@code shenyu.plugin}.
+     *
+     * @param namespaceId namespace id
+     * @return path key set with namespace prefix
+     */
+    public static Set<String> pathKeySet(final String namespaceId) {
+        final Set<String> namespacedPathKeySet = new HashSet<>();
+        pathKeySet().forEach(dataId -> namespacedPathKeySet.add(namespaceId + 
DefaultNodeConstants.JOIN_POINT + dataId));
+        return namespacedPathKeySet;
+    }
 }
 
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-api/src/main/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncService.java
 
b/shenyu-sync-data-center/shenyu-sync-data-api/src/main/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncService.java
index 17a0e6d5cf..98e6926143 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-api/src/main/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncService.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-api/src/main/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncService.java
@@ -216,9 +216,14 @@ public abstract class AbstractNodeDataSyncService {
                 .flatMap(data -> 
Optional.ofNullable(pluginDataSubscriber)).ifPresent(e -> 
e.onSubscribe(pluginData));
     }
 
-    protected void unCachePluginData(final String pluginName) {
+    protected void unCachePluginData(final String removeKey) {
+        final String[] pluginKeys = StringUtils.split(removeKey, 
DefaultNodeConstants.JOIN_POINT);
+        if (Objects.isNull(pluginKeys) || pluginKeys.length < 3) {
+            LOG.warn("AbstractNodeDataSyncService invalid plugin data remove 
key: {}", removeKey);
+            return;
+        }
         final PluginData data = new PluginData();
-        data.setName(pluginName);
+        data.setName(pluginKeys[2]);
         Optional.ofNullable(pluginDataSubscriber).ifPresent(e -> 
e.unSubscribe(data));
     }
 
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-api/src/test/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncServiceTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-api/src/test/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncServiceTest.java
index 79178226d5..0673fd5079 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-api/src/test/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncServiceTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-api/src/test/java/org/apache/shenyu/sync/data/core/AbstractNodeDataSyncServiceTest.java
@@ -116,13 +116,11 @@ public class AbstractNodeDataSyncServiceTest {
     @Test
     public void testUnCachePluginData() {
 
-        String pluginName = "testPlugin";
-
-        nodeDataSyncService.unCachePluginData(pluginName);
+        nodeDataSyncService.unCachePluginData("namespace.plugin.testPlugin");
 
         ArgumentCaptor<PluginData> captor = 
ArgumentCaptor.forClass(PluginData.class);
         verify(pluginDataSubscriber).unSubscribe(captor.capture());
-        assertEquals(pluginName, captor.getValue().getName());
+        assertEquals("testPlugin", captor.getValue().getName());
     }
 
     @Test
@@ -165,11 +163,12 @@ public class AbstractNodeDataSyncServiceTest {
 
     @Test
     public void testUnCacheDataWithInvalidKey() {
+        assertDoesNotThrow(() -> 
nodeDataSyncService.unCachePluginData("namespace"));
         assertDoesNotThrow(() -> 
nodeDataSyncService.unCacheAuthData("namespace"));
         assertDoesNotThrow(() -> 
nodeDataSyncService.unCacheMetaData("namespace"));
         assertDoesNotThrow(() -> 
nodeDataSyncService.unCacheProxySelectorData("namespace"));
 
-        verifyNoInteractions(authDataSubscriber, metaDataSubscriber, 
proxySelectorDataSubscriber);
+        verifyNoInteractions(pluginDataSubscriber, authDataSubscriber, 
metaDataSubscriber, proxySelectorDataSubscriber);
     }
 
     // Mock implementation
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-apollo/src/main/java/org/apache/shenyu/sync/data/apollo/ApolloDataService.java
 
b/shenyu-sync-data-center/shenyu-sync-data-apollo/src/main/java/org/apache/shenyu/sync/data/apollo/ApolloDataService.java
index bc53f01ad1..e2f8680426 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-apollo/src/main/java/org/apache/shenyu/sync/data/apollo/ApolloDataService.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-apollo/src/main/java/org/apache/shenyu/sync/data/apollo/ApolloDataService.java
@@ -81,10 +81,12 @@ public class ApolloDataService extends 
AbstractNodeDataSyncService implements Sy
         this.configService = configService;
 
         startWatch();
-        apolloWatchPrefixes();
+        apolloWatchPrefixes(shenyuConfig.getNamespace());
     }
 
-    private void apolloWatchPrefixes() {
+    private void apolloWatchPrefixes(final String namespaceId) {
+        // apollo change keys are namespace-prefixed, e.g. 
{namespaceId}.plugin.{pluginName}
+        final String namespacePrefix = namespaceId + 
DefaultNodeConstants.JOIN_POINT;
         final ConfigChangeListener listener = changeEvent -> 
changeEvent.changedKeys().forEach(changeKey -> {
             try {
                 final ConfigChange configChange = 
changeEvent.getChange(changeKey);
@@ -99,43 +101,43 @@ public class ApolloDataService extends 
AbstractNodeDataSyncService implements Sy
                     return;
                 }
                 // check prefix
-                if (changeKey.indexOf(ApolloPathConstants.PLUGIN_DATA_ID) == 
0) {
+                if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.PLUGIN_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCachePluginData(changeKey);
                     } else {
                         cachePluginData(newValue);
                     }
-                } else if 
(changeKey.indexOf(ApolloPathConstants.SELECTOR_DATA_ID) == 0) {
+                } else if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.SELECTOR_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCacheSelectorData(changeKey);
                     } else {
                         cacheSelectorData(newValue);
                     }
-                } else if (changeKey.indexOf(ApolloPathConstants.RULE_DATA_ID) 
== 0) {
+                } else if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.RULE_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCacheRuleData(changeKey);
                     } else {
                         cacheRuleData(newValue);
                     }
-                } else if (changeKey.indexOf(ApolloPathConstants.AUTH_DATA_ID) 
== 0) {
+                } else if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.AUTH_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCacheAuthData(changeKey);
                     } else {
                         cacheAuthData(newValue);
                     }
-                } else if (changeKey.indexOf(ApolloPathConstants.META_DATA_ID) 
== 0) {
+                } else if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.META_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCacheMetaData(changeKey);
                     } else {
                         cacheMetaData(newValue);
                     }
-                } else if 
(changeKey.indexOf(ApolloPathConstants.PROXY_SELECTOR_DATA_ID) == 0) {
+                } else if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.PROXY_SELECTOR_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCacheProxySelectorData(changeKey);
                     } else {
                         cacheProxySelectorData(newValue);
                     }
-                } else if 
(changeKey.indexOf(ApolloPathConstants.DISCOVERY_DATA_ID) == 0) {
+                } else if (changeKey.startsWith(namespacePrefix + 
ApolloPathConstants.DISCOVERY_DATA_ID)) {
                     if 
(PropertyChangeType.DELETED.equals(configChange.getChangeType())) {
                         unCacheDiscoveryUpstreamData(changeKey);
                     } else {
@@ -147,7 +149,7 @@ public class ApolloDataService extends 
AbstractNodeDataSyncService implements Sy
             }
         });
         watchConfigChangeListener = listener;
-        configService.addChangeListener(listener, Collections.emptySet(), 
ApolloPathConstants.pathKeySet());
+        configService.addChangeListener(listener, Collections.emptySet(), 
ApolloPathConstants.pathKeySet(namespaceId));
 
     }
 
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceIntegrationTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceIntegrationTest.java
new file mode 100644
index 0000000000..eb99b83e05
--- /dev/null
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceIntegrationTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.sync.data.apollo;
+
+import com.ctrip.framework.apollo.enums.ConfigSourceType;
+import com.ctrip.framework.apollo.internals.ConfigRepository;
+import com.ctrip.framework.apollo.internals.DefaultConfig;
+import org.apache.shenyu.common.config.ShenyuConfig;
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.sync.data.api.AuthDataSubscriber;
+import org.apache.shenyu.sync.data.api.DiscoveryUpstreamDataSubscriber;
+import org.apache.shenyu.sync.data.api.MetaDataSubscriber;
+import org.apache.shenyu.sync.data.api.PluginDataSubscriber;
+import org.apache.shenyu.sync.data.api.ProxySelectorDataSubscriber;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Collections;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Integration test case for {@link ApolloDataService} that flows through 
apollo-client's real
+ * change listener filtering (interested keys and prefixes are matched in 
{@link com.ctrip.framework.apollo.internals.AbstractConfig}).
+ */
+@ExtendWith(MockitoExtension.class)
+class ApolloDataServiceIntegrationTest {
+
+    private static final String NAMESPACE = "shenyu";
+
+    @Mock
+    private PluginDataSubscriber pluginDataSubscriber;
+
+    @Mock
+    private MetaDataSubscriber metaDataSubscriber;
+
+    @Mock
+    private AuthDataSubscriber authDataSubscriber;
+
+    @Mock
+    private ProxySelectorDataSubscriber proxySelectorDataSubscriber;
+
+    @Mock
+    private DiscoveryUpstreamDataSubscriber discoveryUpstreamDataSubscriber;
+
+    @Test
+    void testIncrementalChangeEventFlowsThroughApolloPrefixFiltering() throws 
InterruptedException {
+        final DefaultConfig defaultConfig = createDefaultConfig();
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            latch.countDown();
+            return null;
+        }).when(pluginDataSubscriber).onSubscribe(any());
+
+        new ApolloDataService(defaultConfig,
+                pluginDataSubscriber,
+                Collections.singletonList(metaDataSubscriber),
+                Collections.singletonList(authDataSubscriber),
+                Collections.singletonList(proxySelectorDataSubscriber),
+                Collections.singletonList(discoveryUpstreamDataSubscriber),
+                createShenyuConfig());
+
+        final Properties changedProperties = new Properties();
+        changedProperties.setProperty(NAMESPACE + ".plugin.divide", 
"{\"id\":\"1\",\"name\":\"divide\"}");
+        defaultConfig.onRepositoryChange(NAMESPACE, changedProperties);
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS),
+                "incremental change event should reach the subscriber through 
apollo prefix filtering");
+    }
+
+    @Test
+    void testDeletedEventFlowsThroughApolloPrefixFiltering() throws 
InterruptedException {
+        final DefaultConfig defaultConfig = createDefaultConfig();
+        final Properties initialProperties = new Properties();
+        initialProperties.setProperty(NAMESPACE + ".plugin.divide", 
"{\"id\":\"1\",\"name\":\"divide\"}");
+        defaultConfig.onRepositoryChange(NAMESPACE, initialProperties);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        doAnswer(invocation -> {
+            latch.countDown();
+            return null;
+        }).when(pluginDataSubscriber).unSubscribe(any());
+
+        new ApolloDataService(defaultConfig,
+                pluginDataSubscriber,
+                Collections.singletonList(metaDataSubscriber),
+                Collections.singletonList(authDataSubscriber),
+                Collections.singletonList(proxySelectorDataSubscriber),
+                Collections.singletonList(discoveryUpstreamDataSubscriber),
+                createShenyuConfig());
+
+        defaultConfig.onRepositoryChange(NAMESPACE, new Properties());
+
+        assertTrue(latch.await(5, TimeUnit.SECONDS),
+                "deleted event should reach the subscriber through apollo 
prefix filtering");
+        ArgumentCaptor<PluginData> captor = 
ArgumentCaptor.forClass(PluginData.class);
+        verify(pluginDataSubscriber).unSubscribe(captor.capture());
+        assertEquals("divide", captor.getValue().getName());
+    }
+
+    private DefaultConfig createDefaultConfig() {
+        final ConfigRepository configRepository = mock(ConfigRepository.class);
+        when(configRepository.getConfig()).thenReturn(new Properties());
+        
when(configRepository.getSourceType()).thenReturn(ConfigSourceType.LOCAL);
+        return new DefaultConfig(NAMESPACE, configRepository);
+    }
+
+    private ShenyuConfig createShenyuConfig() {
+        final ShenyuConfig shenyuConfig = new ShenyuConfig();
+        shenyuConfig.setNamespace(NAMESPACE);
+        return shenyuConfig;
+    }
+}
diff --git 
a/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceTest.java
 
b/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceTest.java
index b2cbfad860..9db5392159 100644
--- 
a/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceTest.java
+++ 
b/shenyu-sync-data-center/shenyu-sync-data-apollo/src/test/java/org/apache/shenyu/sync/data/apollo/ApolloDataServiceTest.java
@@ -24,6 +24,9 @@ import com.ctrip.framework.apollo.model.ConfigChange;
 import com.ctrip.framework.apollo.model.ConfigChangeEvent;
 import org.apache.shenyu.common.config.ShenyuConfig;
 import org.apache.shenyu.common.constant.ApolloPathConstants;
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.ProxySelectorData;
+import org.apache.shenyu.common.dto.RuleData;
 import org.apache.shenyu.sync.data.api.AuthDataSubscriber;
 import org.apache.shenyu.sync.data.api.DiscoveryUpstreamDataSubscriber;
 import org.apache.shenyu.sync.data.api.MetaDataSubscriber;
@@ -40,6 +43,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
 
+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.ArgumentMatchers.anySet;
@@ -49,6 +53,7 @@ import static org.mockito.Mockito.lenient;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 /**
@@ -57,6 +62,8 @@ import static org.mockito.Mockito.when;
 @ExtendWith(MockitoExtension.class)
 class ApolloDataServiceTest {
 
+    private static final String NAMESPACE = "shenyu";
+
     @Mock
     private Config configService;
 
@@ -82,7 +89,7 @@ class ApolloDataServiceTest {
     @BeforeEach
     void setUp() {
         shenyuConfig = new ShenyuConfig();
-        shenyuConfig.setNamespace("shenyu");
+        shenyuConfig.setNamespace(NAMESPACE);
 
         lenient().when(configService.getProperty(anyString(), 
any())).thenReturn("[]");
     }
@@ -100,7 +107,7 @@ class ApolloDataServiceTest {
         );
 
         ArgumentCaptor<ConfigChangeListener> listenerCaptor = 
ArgumentCaptor.forClass(ConfigChangeListener.class);
-        verify(configService).addChangeListener(listenerCaptor.capture(), 
anySet(), eq(ApolloPathConstants.pathKeySet()));
+        verify(configService).addChangeListener(listenerCaptor.capture(), 
anySet(), eq(ApolloPathConstants.pathKeySet(NAMESPACE)));
 
         capturedListener = listenerCaptor.getValue();
         assertNotNull(capturedListener);
@@ -132,7 +139,7 @@ class ApolloDataServiceTest {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.PLUGIN_DATA_ID + "/test-plugin",
+                NAMESPACE + "." + ApolloPathConstants.PLUGIN_DATA_ID + 
".test-plugin",
                 "{\"id\":\"1\",\"name\":\"test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -146,7 +153,7 @@ class ApolloDataServiceTest {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.PLUGIN_DATA_ID + "/new-plugin",
+                NAMESPACE + "." + ApolloPathConstants.PLUGIN_DATA_ID + 
".new-plugin",
                 "{\"id\":\"2\",\"name\":\"new\"}",
                 PropertyChangeType.ADDED
         );
@@ -155,12 +162,28 @@ class ApolloDataServiceTest {
         verify(pluginDataSubscriber, times(1)).onSubscribe(any());
     }
 
+    @Test
+    void testPluginDataDeleted() {
+        ApolloDataService apolloDataService = createApolloDataService();
+
+        ConfigChangeEvent event = mockConfigChangeEvent(
+                NAMESPACE + "." + ApolloPathConstants.PLUGIN_DATA_ID + 
".test-plugin",
+                null,
+                PropertyChangeType.DELETED
+        );
+
+        capturedListener.onChange(event);
+        ArgumentCaptor<PluginData> captor = 
ArgumentCaptor.forClass(PluginData.class);
+        verify(pluginDataSubscriber, times(1)).unSubscribe(captor.capture());
+        assertEquals("test-plugin", captor.getValue().getName());
+    }
+
     @Test
     void testSelectorDataChange() {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.SELECTOR_DATA_ID + "/test-selector",
+                NAMESPACE + "." + ApolloPathConstants.SELECTOR_DATA_ID + 
".test-plugin.test-selector",
                 "{\"id\":\"1\",\"name\":\"test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -174,7 +197,7 @@ class ApolloDataServiceTest {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.RULE_DATA_ID + "/test-rule",
+                NAMESPACE + "." + ApolloPathConstants.RULE_DATA_ID + 
".test-plugin.test-selector.test-rule",
                 "{\"id\":\"1\",\"name\":\"test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -183,12 +206,30 @@ class ApolloDataServiceTest {
         verify(pluginDataSubscriber, times(1)).onRuleSubscribe(any());
     }
 
+    @Test
+    void testRuleDataDeleted() {
+        ApolloDataService apolloDataService = createApolloDataService();
+
+        ConfigChangeEvent event = mockConfigChangeEvent(
+                NAMESPACE + "." + ApolloPathConstants.RULE_DATA_ID + 
".test-plugin.test-selector.test-rule",
+                null,
+                PropertyChangeType.DELETED
+        );
+
+        capturedListener.onChange(event);
+        ArgumentCaptor<RuleData> captor = 
ArgumentCaptor.forClass(RuleData.class);
+        verify(pluginDataSubscriber, 
times(1)).unRuleSubscribe(captor.capture());
+        assertEquals("test-plugin", captor.getValue().getPluginName());
+        assertEquals("test-selector", captor.getValue().getSelectorId());
+        assertEquals("test-rule", captor.getValue().getId());
+    }
+
     @Test
     void testAuthDataChange() {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.AUTH_DATA_ID + "/test-auth",
+                NAMESPACE + "." + ApolloPathConstants.AUTH_DATA_ID + 
".test-auth",
                 "{\"appKey\":\"test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -202,7 +243,7 @@ class ApolloDataServiceTest {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.META_DATA_ID + "/test-meta",
+                NAMESPACE + "." + ApolloPathConstants.META_DATA_ID + 
".test-meta",
                 "{\"id\":\"1\",\"path\":\"/test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -216,7 +257,7 @@ class ApolloDataServiceTest {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.PROXY_SELECTOR_DATA_ID + "/test-proxy",
+                NAMESPACE + "." + ApolloPathConstants.PROXY_SELECTOR_DATA_ID + 
".test-plugin.test-proxy",
                 "{\"id\":\"1\",\"name\":\"test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -225,12 +266,29 @@ class ApolloDataServiceTest {
         verify(proxySelectorDataSubscriber, times(1)).onSubscribe(any());
     }
 
+    @Test
+    void testProxySelectorDataDeleted() {
+        ApolloDataService apolloDataService = createApolloDataService();
+
+        ConfigChangeEvent event = mockConfigChangeEvent(
+                NAMESPACE + "." + ApolloPathConstants.PROXY_SELECTOR_DATA_ID + 
".test-plugin.test-proxy",
+                null,
+                PropertyChangeType.DELETED
+        );
+
+        capturedListener.onChange(event);
+        ArgumentCaptor<ProxySelectorData> captor = 
ArgumentCaptor.forClass(ProxySelectorData.class);
+        verify(proxySelectorDataSubscriber, 
times(1)).unSubscribe(captor.capture());
+        assertEquals("test-plugin", captor.getValue().getPluginName());
+        assertEquals("test-proxy", captor.getValue().getName());
+    }
+
     @Test
     void testDiscoveryDataChange() {
         ApolloDataService apolloDataService = createApolloDataService();
 
         ConfigChangeEvent event = mockConfigChangeEvent(
-                ApolloPathConstants.DISCOVERY_DATA_ID + "/test-discovery",
+                NAMESPACE + "." + ApolloPathConstants.DISCOVERY_DATA_ID + 
".test-plugin.test-discovery",
                 "{\"name\":\"test\"}",
                 PropertyChangeType.MODIFIED
         );
@@ -239,6 +297,34 @@ class ApolloDataServiceTest {
         verify(discoveryUpstreamDataSubscriber, times(1)).onSubscribe(any());
     }
 
+    @Test
+    void testListKeySkipped() {
+        ApolloDataService apolloDataService = createApolloDataService();
+
+        ConfigChangeEvent event = mockConfigChangeEvent(
+                NAMESPACE + "." + ApolloPathConstants.PLUGIN_DATA_ID + ".list",
+                "[\"test-plugin\"]",
+                PropertyChangeType.MODIFIED
+        );
+
+        capturedListener.onChange(event);
+        verifyNoInteractions(pluginDataSubscriber);
+    }
+
+    @Test
+    void testChangeKeyWithoutNamespacePrefixIgnored() {
+        ApolloDataService apolloDataService = createApolloDataService();
+
+        ConfigChangeEvent event = mockConfigChangeEvent(
+                "other-namespace." + ApolloPathConstants.PLUGIN_DATA_ID + 
".test-plugin",
+                "{\"id\":\"1\",\"name\":\"test\"}",
+                PropertyChangeType.MODIFIED
+        );
+
+        capturedListener.onChange(event);
+        verifyNoInteractions(pluginDataSubscriber);
+    }
+
     @Test
     void testNullConfigChange() {
         final ApolloDataService apolloDataService = createApolloDataService();
@@ -260,7 +346,7 @@ class ApolloDataServiceTest {
 
         ConfigChangeEvent event = mock(ConfigChangeEvent.class);
         Set<String> keys = new HashSet<>();
-        keys.add(ApolloPathConstants.PLUGIN_DATA_ID + "/test");
+        keys.add(NAMESPACE + "." + ApolloPathConstants.PLUGIN_DATA_ID + 
".test");
         when(event.changedKeys()).thenReturn(keys);
         when(event.getChange(anyString())).thenThrow(new 
RuntimeException("Test exception"));
 
@@ -281,7 +367,7 @@ class ApolloDataServiceTest {
         );
 
         ArgumentCaptor<ConfigChangeListener> listenerCaptor = 
ArgumentCaptor.forClass(ConfigChangeListener.class);
-        verify(configService).addChangeListener(listenerCaptor.capture(), 
anySet(), eq(ApolloPathConstants.pathKeySet()));
+        verify(configService).addChangeListener(listenerCaptor.capture(), 
anySet(), eq(ApolloPathConstants.pathKeySet(NAMESPACE)));
         capturedListener = listenerCaptor.getValue();
 
         return service;
@@ -297,7 +383,7 @@ class ApolloDataServiceTest {
         when(event.changedKeys()).thenReturn(keys);
         when(event.getChange(key)).thenReturn(configChange);
         when(configChange.getNewValue()).thenReturn(newValue);
-        when(configChange.getChangeType()).thenReturn(changeType);
+        lenient().when(configChange.getChangeType()).thenReturn(changeType);
 
         return event;
     }

Reply via email to