package com.ctrip.framework.apollo.internals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletResponse; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import com.ctrip.framework.apollo.build.MockInjector; import com.ctrip.framework.apollo.core.dto.ApolloConfig; import com.ctrip.framework.apollo.core.dto.ApolloConfigNotification; import com.ctrip.framework.apollo.core.dto.ServiceDTO; import com.ctrip.framework.apollo.exceptions.ApolloConfigException; import com.ctrip.framework.apollo.util.ConfigUtil; import com.ctrip.framework.apollo.util.http.HttpRequest; import com.ctrip.framework.apollo.util.http.HttpResponse; import com.ctrip.framework.apollo.util.http.HttpUtil; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.util.concurrent.SettableFuture; /** * Created by Jason on 4/9/16. */ @RunWith(MockitoJUnitRunner.class) public class RemoteConfigRepositoryTest { @Mock private ConfigServiceLocator configServiceLocator; private String someNamespace; @Mock private static HttpResponse<ApolloConfig> someResponse; @Mock private static HttpResponse<List<ApolloConfigNotification>> pollResponse; private RemoteConfigLongPollService remoteConfigLongPollService; @Before public void setUp() throws Exception { someNamespace = "someName"; when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_NOT_MODIFIED); MockInjector.reset(); MockInjector.setInstance(ConfigUtil.class, new MockConfigUtil()); String someServerUrl = "http://someServer"; ServiceDTO serviceDTO = mock(ServiceDTO.class); when(serviceDTO.getHomepageUrl()).thenReturn(someServerUrl); when(configServiceLocator.getConfigServices()).thenReturn(Lists.newArrayList(serviceDTO)); MockInjector.setInstance(ConfigServiceLocator.class, configServiceLocator); MockInjector.setInstance(HttpUtil.class, new MockHttpUtil()); remoteConfigLongPollService = new RemoteConfigLongPollService(); MockInjector.setInstance(RemoteConfigLongPollService.class, remoteConfigLongPollService); } @Test public void testLoadConfig() throws Exception { String someKey = "someKey"; String someValue = "someValue"; Map<String, String> configurations = Maps.newHashMap(); configurations.put(someKey, someValue); ApolloConfig someApolloConfig = assembleApolloConfig(configurations); when(someResponse.getStatusCode()).thenReturn(200); when(someResponse.getBody()).thenReturn(someApolloConfig); RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); Properties config = remoteConfigRepository.getConfig(); assertEquals(configurations, config); remoteConfigLongPollService.stopLongPollingRefresh(); } @Test(expected = ApolloConfigException.class) public void testGetRemoteConfigWithServerError() throws Exception { when(someResponse.getStatusCode()).thenReturn(500); RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); //must stop the long polling before exception occurred remoteConfigLongPollService.stopLongPollingRefresh(); remoteConfigRepository.getConfig(); } @Test public void testRepositoryChangeListener() throws Exception { Map<String, String> configurations = ImmutableMap.of("someKey", "someValue"); ApolloConfig someApolloConfig = assembleApolloConfig(configurations); when(someResponse.getStatusCode()).thenReturn(200); when(someResponse.getBody()).thenReturn(someApolloConfig); RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); remoteConfigRepository.addChangeListener(someListener); final ArgumentCaptor<Properties> captor = ArgumentCaptor.forClass(Properties.class); Map<String, String> newConfigurations = ImmutableMap.of("someKey", "anotherValue"); ApolloConfig newApolloConfig = assembleApolloConfig(newConfigurations); when(someResponse.getBody()).thenReturn(newApolloConfig); remoteConfigRepository.sync(); verify(someListener, times(1)).onRepositoryChange(eq(someNamespace), captor.capture()); assertEquals(newConfigurations, captor.getValue()); remoteConfigLongPollService.stopLongPollingRefresh(); } @Test public void testLongPollingRefresh() throws Exception { Map<String, String> configurations = ImmutableMap.of("someKey", "someValue"); ApolloConfig someApolloConfig = assembleApolloConfig(configurations); when(someResponse.getStatusCode()).thenReturn(200); when(someResponse.getBody()).thenReturn(someApolloConfig); final SettableFuture<Boolean> longPollFinished = SettableFuture.create(); RepositoryChangeListener someListener = mock(RepositoryChangeListener.class); doAnswer(new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) throws Throwable { longPollFinished.set(true); return null; } }).when(someListener).onRepositoryChange(any(String.class), any(Properties.class)); RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); remoteConfigRepository.addChangeListener(someListener); final ArgumentCaptor<Properties> captor = ArgumentCaptor.forClass(Properties.class); Map<String, String> newConfigurations = ImmutableMap.of("someKey", "anotherValue"); ApolloConfig newApolloConfig = assembleApolloConfig(newConfigurations); ApolloConfigNotification someNotification = mock(ApolloConfigNotification.class); when(someNotification.getNamespaceName()).thenReturn(someNamespace); when(pollResponse.getStatusCode()).thenReturn(HttpServletResponse.SC_OK); when(pollResponse.getBody()).thenReturn(Lists.newArrayList(someNotification)); when(someResponse.getBody()).thenReturn(newApolloConfig); longPollFinished.get(500, TimeUnit.MILLISECONDS); remoteConfigLongPollService.stopLongPollingRefresh(); verify(someListener, times(1)).onRepositoryChange(eq(someNamespace), captor.capture()); assertEquals(newConfigurations, captor.getValue()); } @Test public void testAssembleQueryConfigUrl() throws Exception { String someUri = "http://someServer"; String someAppId = "someAppId"; String someCluster = "someCluster+ &.-_someSign"; String someReleaseKey = "20160705193346-583078ef5716c055+20160705193308-31c471ddf9087c3f"; RemoteConfigRepository remoteConfigRepository = new RemoteConfigRepository(someNamespace); ApolloConfig someApolloConfig = mock(ApolloConfig.class); when(someApolloConfig.getReleaseKey()).thenReturn(someReleaseKey); String queryConfigUrl = remoteConfigRepository .assembleQueryConfigUrl(someUri, someAppId, someCluster, someNamespace, null, someApolloConfig); remoteConfigLongPollService.stopLongPollingRefresh(); assertTrue(queryConfigUrl .contains( "http://someServer/configs/someAppId/someCluster+%20&.-_someSign/" + someNamespace)); assertTrue(queryConfigUrl .contains("releaseKey=20160705193346-583078ef5716c055%2B20160705193308-31c471ddf9087c3f")); } private ApolloConfig assembleApolloConfig(Map<String, String> configurations) { String someAppId = "appId"; String someClusterName = "cluster"; String someReleaseKey = "1"; ApolloConfig apolloConfig = new ApolloConfig(someAppId, someClusterName, someNamespace, someReleaseKey); apolloConfig.setConfigurations(configurations); return apolloConfig; } public static class MockConfigUtil extends ConfigUtil { @Override public String getAppId() { return "someApp"; } @Override public String getCluster() { return "someCluster"; } @Override public String getDataCenter() { return null; } @Override public int getLoadConfigQPS() { return 200; } @Override public int getLongPollQPS() { return 200; } @Override public long getOnErrorRetryInterval() { return 10; } @Override public TimeUnit getOnErrorRetryIntervalTimeUnit() { return TimeUnit.MILLISECONDS; } } public static class MockHttpUtil extends HttpUtil { @Override public <T> HttpResponse<T> doGet(HttpRequest httpRequest, Class<T> responseType) { if (someResponse.getStatusCode() == 200 || someResponse.getStatusCode() == 304 ) { return (HttpResponse<T>) someResponse; } throw new ApolloConfigException(String.format("Http request failed due to status code: %d", someResponse.getStatusCode())); } @Override public <T> HttpResponse<T> doGet(HttpRequest httpRequest, Type responseType) { try { TimeUnit.MILLISECONDS.sleep(50); } catch (InterruptedException e) { } return (HttpResponse<T>) pollResponse; } } }