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 bb04ee0000ab..b2cbfad860e9 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 @@ -18,27 +18,288 @@ package org.apache.shenyu.sync.data.apollo; import com.ctrip.framework.apollo.Config; +import com.ctrip.framework.apollo.ConfigChangeListener; +import com.ctrip.framework.apollo.enums.PropertyChangeType; +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.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.junit.Test; +import org.apache.shenyu.sync.data.api.ProxySelectorDataSubscriber; +import org.junit.jupiter.api.BeforeEach; +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.List; +import java.util.HashSet; +import java.util.Set; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anySet; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +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.when; -public class ApolloDataServiceTest { +/** + * Test case for {@link ApolloDataService}. + */ +@ExtendWith(MockitoExtension.class) +class ApolloDataServiceTest { + + @Mock + private Config configService; + + @Mock + private PluginDataSubscriber pluginDataSubscriber; + + @Mock + private MetaDataSubscriber metaDataSubscriber; + + @Mock + private AuthDataSubscriber authDataSubscriber; + + @Mock + private ProxySelectorDataSubscriber proxySelectorDataSubscriber; + + @Mock + private DiscoveryUpstreamDataSubscriber discoveryUpstreamDataSubscriber; + + private ShenyuConfig shenyuConfig; + + private ConfigChangeListener capturedListener; + + @BeforeEach + void setUp() { + shenyuConfig = new ShenyuConfig(); + shenyuConfig.setNamespace("shenyu"); + + lenient().when(configService.getProperty(anyString(), any())).thenReturn("[]"); + } + + @Test + void testConstructorAndClose() { + final ApolloDataService apolloDataService = new ApolloDataService( + configService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigChangeListener.class); + verify(configService).addChangeListener(listenerCaptor.capture(), anySet(), eq(ApolloPathConstants.pathKeySet())); + + capturedListener = listenerCaptor.getValue(); + assertNotNull(capturedListener); + + apolloDataService.close(); + verify(configService).removeChangeListener(capturedListener); + } - /** - * Method under test: {@link ApolloDataService#ApolloDataService(Config, PluginDataSubscriber, List, List, List, List)}. - */ @Test - public void testClose() { - Config configService = mock(Config.class); - ShenyuConfig shenyuConfig = new ShenyuConfig(); - ApolloDataService apolloDataService = new ApolloDataService(configService, null, - Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), shenyuConfig); + void testCloseWithNullListener() { + lenient().when(configService.getProperty(anyString(), any())).thenReturn(null); + + ApolloDataService apolloDataService = new ApolloDataService( + configService, + null, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + shenyuConfig + ); + apolloDataService.close(); + verify(configService, times(1)).removeChangeListener(any()); + } + + @Test + void testPluginDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.PLUGIN_DATA_ID + "/test-plugin", + "{\"id\":\"1\",\"name\":\"test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(pluginDataSubscriber, times(1)).onSubscribe(any()); + } + + @Test + void testPluginDataAdd() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.PLUGIN_DATA_ID + "/new-plugin", + "{\"id\":\"2\",\"name\":\"new\"}", + PropertyChangeType.ADDED + ); + + capturedListener.onChange(event); + verify(pluginDataSubscriber, times(1)).onSubscribe(any()); + } + + @Test + void testSelectorDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.SELECTOR_DATA_ID + "/test-selector", + "{\"id\":\"1\",\"name\":\"test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(pluginDataSubscriber, times(1)).onSelectorSubscribe(any()); + } + + @Test + void testRuleDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.RULE_DATA_ID + "/test-rule", + "{\"id\":\"1\",\"name\":\"test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(pluginDataSubscriber, times(1)).onRuleSubscribe(any()); + } + + @Test + void testAuthDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.AUTH_DATA_ID + "/test-auth", + "{\"appKey\":\"test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(authDataSubscriber, times(1)).onSubscribe(any()); + } + + @Test + void testMetaDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.META_DATA_ID + "/test-meta", + "{\"id\":\"1\",\"path\":\"/test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(metaDataSubscriber, times(1)).onSubscribe(any()); + } + + @Test + void testProxySelectorDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.PROXY_SELECTOR_DATA_ID + "/test-proxy", + "{\"id\":\"1\",\"name\":\"test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(proxySelectorDataSubscriber, times(1)).onSubscribe(any()); + } + + @Test + void testDiscoveryDataChange() { + ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mockConfigChangeEvent( + ApolloPathConstants.DISCOVERY_DATA_ID + "/test-discovery", + "{\"name\":\"test\"}", + PropertyChangeType.MODIFIED + ); + + capturedListener.onChange(event); + verify(discoveryUpstreamDataSubscriber, times(1)).onSubscribe(any()); + } + + @Test + void testNullConfigChange() { + final ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mock(ConfigChangeEvent.class); + Set keys = new HashSet<>(); + keys.add("test-key"); + when(event.changedKeys()).thenReturn(keys); + when(event.getChange("test-key")).thenReturn(null); + + capturedListener.onChange(event); + // Should handle null config change gracefully + assertNotNull(apolloDataService); + } + + @Test + void testExceptionInChangeHandler() { + final ApolloDataService apolloDataService = createApolloDataService(); + + ConfigChangeEvent event = mock(ConfigChangeEvent.class); + Set keys = new HashSet<>(); + keys.add(ApolloPathConstants.PLUGIN_DATA_ID + "/test"); + when(event.changedKeys()).thenReturn(keys); + when(event.getChange(anyString())).thenThrow(new RuntimeException("Test exception")); + + capturedListener.onChange(event); + // Should handle exception gracefully + assertNotNull(apolloDataService); + } + + private ApolloDataService createApolloDataService() { + ApolloDataService service = new ApolloDataService( + configService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigChangeListener.class); + verify(configService).addChangeListener(listenerCaptor.capture(), anySet(), eq(ApolloPathConstants.pathKeySet())); + capturedListener = listenerCaptor.getValue(); + + return service; + } + + private ConfigChangeEvent mockConfigChangeEvent(final String key, final String newValue, final PropertyChangeType changeType) { + ConfigChangeEvent event = mock(ConfigChangeEvent.class); + ConfigChange configChange = mock(ConfigChange.class); + + Set keys = new HashSet<>(); + keys.add(key); + + when(event.changedKeys()).thenReturn(keys); + when(event.getChange(key)).thenReturn(configChange); + when(configChange.getNewValue()).thenReturn(newValue); + when(configChange.getChangeType()).thenReturn(changeType); + + return event; } } diff --git a/shenyu-sync-data-center/shenyu-sync-data-polaris/src/test/java/org/apache/shenyu/sync/data/polaris/PolarisSyncDataServiceTest.java b/shenyu-sync-data-center/shenyu-sync-data-polaris/src/test/java/org/apache/shenyu/sync/data/polaris/PolarisSyncDataServiceTest.java index 32715a258a99..12160beb0111 100644 --- a/shenyu-sync-data-center/shenyu-sync-data-polaris/src/test/java/org/apache/shenyu/sync/data/polaris/PolarisSyncDataServiceTest.java +++ b/shenyu-sync-data-center/shenyu-sync-data-polaris/src/test/java/org/apache/shenyu/sync/data/polaris/PolarisSyncDataServiceTest.java @@ -17,34 +17,464 @@ package org.apache.shenyu.sync.data.polaris; +import com.tencent.polaris.configuration.api.core.ChangeType; +import com.tencent.polaris.configuration.api.core.ConfigFile; +import com.tencent.polaris.configuration.api.core.ConfigFileChangeEvent; +import com.tencent.polaris.configuration.api.core.ConfigFileChangeListener; import com.tencent.polaris.configuration.api.core.ConfigFileService; import org.apache.shenyu.common.config.ShenyuConfig; +import org.apache.shenyu.common.exception.ShenyuException; +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.apache.shenyu.sync.data.polaris.config.PolarisConfig; -import org.apache.shenyu.sync.data.polaris.handler.PolarisMockConfigService; import org.junit.jupiter.api.BeforeEach; 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.HashMap; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +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.atLeastOnce; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * add test case for {@link PolarisSyncDataService}. */ -public class PolarisSyncDataServiceTest { +@ExtendWith(MockitoExtension.class) +class PolarisSyncDataServiceTest { + + @Mock + private ConfigFileService configFileService; + + @Mock + private PluginDataSubscriber pluginDataSubscriber; + + @Mock + private MetaDataSubscriber metaDataSubscriber; + + @Mock + private AuthDataSubscriber authDataSubscriber; + + @Mock + private ProxySelectorDataSubscriber proxySelectorDataSubscriber; + + @Mock + private DiscoveryUpstreamDataSubscriber discoveryUpstreamDataSubscriber; - private PolarisSyncDataService polarisSyncDataService; + @Mock + private ConfigFile configFile; + + private PolarisConfig polarisConfig; + + private ShenyuConfig shenyuConfig; @BeforeEach - public void setup() { - PolarisConfig polarisConfig = new PolarisConfig(); - ConfigFileService configFileService = new PolarisMockConfigService(new HashMap<>()); - ShenyuConfig shenyuConfig = new ShenyuConfig(); - polarisSyncDataService = new PolarisSyncDataService(polarisConfig, configFileService, null, - Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), shenyuConfig); + void setup() { + polarisConfig = new PolarisConfig(); + polarisConfig.setNamespace("test-namespace"); + polarisConfig.setFileGroup("test-group"); + shenyuConfig = new ShenyuConfig(); + + lenient().when(configFileService.getConfigFile(anyString(), anyString(), anyString())).thenReturn(configFile); + lenient().when(configFile.hasContent()).thenReturn(true); + lenient().when(configFile.getContent()).thenReturn("[]"); + } + + @Test + void testConstructor() { + PolarisSyncDataService service = new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + assertNotNull(service); + verify(configFileService, atLeastOnce()).getConfigFile(anyString(), anyString(), anyString()); + } + + @Test + void testConfigFileWithNoContent() { + when(configFile.hasContent()).thenReturn(false); + + PolarisSyncDataService service = new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + assertNotNull(service); + } + + @Test + void testListenerUpdateEvent() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.MODIFIED); + when(event.getOldValue()).thenReturn("old"); + when(event.getNewValue()).thenReturn("new"); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testListenerDeleteEvent() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.DELETED); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testListenerBlankNewValue() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.MODIFIED); + when(event.getNewValue()).thenReturn(""); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testListenerSameOldAndNewValue() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.MODIFIED); + when(event.getOldValue()).thenReturn("same"); + when(event.getNewValue()).thenReturn("same"); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testListenerException() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenThrow(new RuntimeException("Test exception")); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testGetServiceConfigException() { + when(configFileService.getConfigFile(anyString(), anyString(), anyString())) + .thenThrow(new RuntimeException("Config error")); + + assertThrows(ShenyuException.class, () -> new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + )); } @Test - public void testClose() { - polarisSyncDataService.close(); + void testClose() { + PolarisSyncDataService service = new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + service.close(); + verify(configFile, atLeastOnce()).removeChangeListener(any(ConfigFileChangeListener.class)); + } + + @Test + void testCloseWithNullListener() { + when(configFile.hasContent()).thenReturn(false); + + PolarisSyncDataService service = new PolarisSyncDataService( + polarisConfig, + configFileService, + null, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + shenyuConfig + ); + + service.close(); + verify(configFile, atLeastOnce()).removeChangeListener(any(ConfigFileChangeListener.class)); + } + + @Test + void testListenerWithDeleteHandler() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.DELETED); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testListenerWithValidUpdate() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.MODIFIED); + when(event.getOldValue()).thenReturn("[]"); + when(event.getNewValue()).thenReturn("[{\"name\":\"test\"}]"); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testGetServiceConfigReturnsContent() { + String expectedContent = "[]"; + when(configFile.hasContent()).thenReturn(true); + when(configFile.getContent()).thenReturn(expectedContent); + + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + verify(configFile, atLeastOnce()).getContent(); + } + + @Test + void testGetServiceConfigReturnsNull() { + when(configFile.hasContent()).thenReturn(false); + + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + verify(configFile, atLeastOnce()).hasContent(); + } + + @Test + void testMultipleConfigFiles() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + verify(configFileService, atLeastOnce()).getConfigFile(anyString(), anyString(), anyString()); + } + + @Test + void testListenerAddedToAllConfigFiles() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + verify(configFile, atLeastOnce()).addChangeListener(any(ConfigFileChangeListener.class)); + } + + @Test + void testCloseRemovesAllListeners() { + PolarisSyncDataService service = new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + service.close(); + + verify(configFileService, atLeastOnce()).getConfigFile(anyString(), anyString(), anyString()); + verify(configFile, atLeastOnce()).removeChangeListener(any(ConfigFileChangeListener.class)); + } + + @Test + void testListenerWithNullOldValue() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.MODIFIED); + when(event.getOldValue()).thenReturn(null); + when(event.getNewValue()).thenReturn("{\"data\":\"new\"}"); + + listenerCaptor.getValue().onChange(event); + } + + @Test + void testListenerWithAddedChangeType() { + new PolarisSyncDataService( + polarisConfig, + configFileService, + pluginDataSubscriber, + Collections.singletonList(metaDataSubscriber), + Collections.singletonList(authDataSubscriber), + Collections.singletonList(proxySelectorDataSubscriber), + Collections.singletonList(discoveryUpstreamDataSubscriber), + shenyuConfig + ); + + ArgumentCaptor listenerCaptor = ArgumentCaptor.forClass(ConfigFileChangeListener.class); + verify(configFile, atLeastOnce()).addChangeListener(listenerCaptor.capture()); + + ConfigFileChangeEvent event = mock(ConfigFileChangeEvent.class); + when(event.getChangeType()).thenReturn(ChangeType.ADDED); + when(event.getNewValue()).thenReturn("{\"data\":\"added\"}"); + when(event.getOldValue()).thenReturn(null); + + listenerCaptor.getValue().onChange(event); } }